How to access Koa context (and koa-session) from Apollo resolvers? - graphql

Given the following Koa application initialization :
const apolloServer = new ApolloServer({
...processSchema(schemas),
...graphqlConfig,
cors: false, // already present with Koa
context: ctx => {
console.log( "*** 2", ctx.session );
// -> *** 2 undefined
}
});
const app = new Koa();
const serverHttp = app
.use(cors(CORS_CONFIG))
.use(session(SESSION_CONFIG, app))
.use(async (ctx, next) => {
console.log( "*** 1", ctx.session );
// *** 1 Session { ...session object }
await next();
if (!ctx.body) {
ctx.throw(404);
}
})
.use(koaBody())
.use(apolloServer.getMiddleware())
.listen(port)
;
As you can see, making any GraphQL query will output
*** 1 Session { ...session object }
*** 2 undefined
Showing that Apollo does not receive the context, neither the session.
Is it possible to have access to the session from context function?
Is it possible to have access to the session from a resolver?

Yes, it is possible to access session from the context function, and here's the way to do it:
const server = new ApolloServer({
typeDefs,
resolvers,
context: (req) => {
const { session } = req.ctx;
// return an object with whatever properties you
// need to be accessible inside resolvers as `context`
return {
userSession: session
}
}
})
Then, inside your resolver, you can access it the following way:
const resolvers = {
Query: {
books: (parent, args, context) {
const { userSession } = context;
const books = [...];
return books;
}
}
}
Hope this helps.

Related

How to use SSR with Redux in Next.js(Typescript) using next-redux-wrapper? [duplicate]

This question already has an answer here:
next-redux-wrapper TypeError: nextCallback is not a function error in wrapper.getServerSideProps
(1 answer)
Closed 1 year ago.
Using redux with SSR in Next.js(Typescript) using next-redux-wrapper, but getting error on this line
async ({ req, store })
Says, Type 'Promise' provides no match for the signature '(context: GetServerSidePropsContext<ParsedUrlQuery, PreviewData>): Promise<GetServerSidePropsResult<{ [key: string]: any; }>>
Property 'req' does not exist on type 'Store<EmptyObject & { filterReducer: never; }, any> & { dispatch: unknown; }'.
Property 'store' does not exist on type 'Store<EmptyObject & { filterReducer: never; }, any> & { dispatch: unknown; }'
Here is my SSR code:-
export const getServerSideProps: GetServerSideProps = wrapper.getServerSideProps(async ({ req, store }) => {
let { query } = req
let searchCategory = query.category?.toString().toLowerCase().replace(/ /g, "-");
const apolloClient = initializeApollo();
const response = await apolloClient.query({
query: GET_PRODUCT_BY_CATEGORY,
variables: {
numProducts: 10,
category: searchCategory
}
});
await store.dispatch(getProducts(response));
});
You're calling wrapper.getServerSideProps in a wrong way.
Try like the following:
export const getServerSideProps = wrapper.getServerSideProps(
store => async ({req, res, query}) => {
// do your stuff with store and req
}
);
If you're looking for a working demo, you can visit my old answer
This code base could help you. ("next": "10.1.3")
Try using getInitialProps instead of getServerSideProps.
This works in my case. Like code below:
Try
in _app.js
import { wrapper } from '/store';
function MyApp(props) {
const { Component, pageProps } = props;
...
return (
<Component {...pageProps} />
)
}
App.getInitialProps = async props => {
const { Component, ctx } = props;
const pageProps = Component.getInitialProps
? await Component.getInitialProps(ctx)
: {};
//Anything returned here can be accessed by the client
return { pageProps: pageProps, store: ctx.store };
};
export default wrapper.withRedux(App);
store.js file:
const makeStore = props => {
if (!isEmpty(props)) {
return createStore(reducer, bindMiddleware([thunkMiddleware]));
} else {
const { persistStore, persistReducer } = require('redux-persist');
const persistConfig = {
key: 'root',
};
const persistedReducer = persistReducer(persistConfig, reducer); // Create a new reducer with our existing reducer
const store = createStore(
persistedReducer,
bindMiddleware([thunkMiddleware])
); // Creating the store again
store.__persistor = persistStore(store); // This creates a persistor object & push that persisted object to .__persistor, so that we can avail the persistability feature
return store;
}
};
// Export the wrapper & wrap the pages/_app.js with this wrapper only
export const wrapper = createWrapper(makeStore);
in your page:
HomePage.getInitialProps = async ctx => {
const { store, query, res } = ctx;
};

Apollo Server - Apply Authentication to Certain Resolvers Only with Passport-JWT

I currently have a Node.js back-end running Express with Passport.js for authentication and am attempting to switch to GraphQL with Apollo Server. My goal is to implement the same authentication I am using currently, but cannot figure out how to leave certain resolvers public while enabling authorization for others. (I have tried researching this question extensively yet have not been able to find a suitable solution thus far.)
Here is my code as it currently stands:
My JWT Strategy:
const opts = {};
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
opts.secretOrKey = JWT_SECRET;
module.exports = passport => {
passport.use(
new JwtStrategy(opts, async (payload, done) => {
try {
const user = await UserModel.findById(payload.sub);
if (!user) {
return done(null, false, { message: "User does not exist!" });
}
done(null, user);
} catch (error) {
done(err, false);
}
})
);
}
My server.js and Apollo configuration:
(I am currently extracting the bearer token from the HTTP headers and passing it along to my resolvers using the context object):
const apollo = new ApolloServer({
typeDefs,
resolvers,
context: async ({ req }) => {
let authToken = "";
try {
if (req.headers.authorization) {
authToken = req.headers.authorization.split(" ")[1];
}
} catch (e) {
console.error("Could not fetch user info", e);
}
return {
authToken
};
}
});
apollo.applyMiddleware({ app });
And finally, my resolvers:
exports.resolvers = {
Query: {
hello() {
return "Hello world!";
},
async getUserInfo(root, args, context) {
try {
const { id } = args;
let user = await UserModel.findById(id);
return user;
} catch (error) {
return "null";
}
},
async events() {
try {
const eventsList = await EventModel.find({});
return eventsList;
} catch (e) {
return [];
}
}
}
};
My goal is to leave certain queries such as the first one ("hello") public while restricting the others to requests with valid bearer tokens only. However, I am not sure how to implement this authorization in the resolvers using Passport.js and Passport-JWT specifically (it is generally done by adding middleware to certain endpoints, however since I would only have one endpoint (/graphql) in this example, that option would restrict all queries to authenticated users only which is not what I am looking for. I have to perform the authorization in the resolvers somehow, yet not sure how to do this with the tools available in Passport.js.)
Any advice is greatly appreciated!
I would create a schema directive to authorized query on field definition and then use that directive wherever I want to apply authorization. Sample code :
class authDirective extends SchemaDirectiveVisitor {
visitObject(type) {
this.ensureFieldsWrapped(type);
type._requiredAuthRole = this.args.requires;
}
visitFieldDefinition(field, details) {
this.ensureFieldsWrapped(details.objectType);
field._requiredAuthRole = this.args.requires;
}
ensureFieldsWrapped(objectType) {
// Mark the GraphQLObjectType object to avoid re-wrapping:
if (objectType._authFieldsWrapped) return;
objectType._authFieldsWrapped = true;
const fields = objectType.getFields();
Object.keys(fields).forEach(fieldName => {
const field = fields[fieldName];
const {
resolve = defaultFieldResolver
} = field;
field.resolve = async function (...args) {
// your authorization code
return resolve.apply(this, args);
};
});
}
}
And declare this in type definition
directive #authorization(requires: String) on OBJECT | FIELD_DEFINITION
map schema directive in your schema
....
resolvers,
schemaDirectives: {
authorization: authDirective
}
Then use it on your api end point or any object
Query: {
hello { ... }
getuserInfo():Result #authorization(requires:authToken) {...}
events():EventResult #authorization(requires:authToken) {...}
};

Stitching secure subscriptions using makeRemoteExecutableSchema

We have implemented schema stitching where GraphQL server fetches schema from two remote servers and stitches them together. Everything was working fine when we were only working with Query and Mutations, but now we have a use-case where we even need to stitch Subscriptions and remote schema has auth implemented over it.
We are having a hard time figuring out on how to pass authorization token received in connectionParams from client to remote server via the gateway.
This is how we are introspecting schema:
API Gateway code:
const getLink = async(): Promise<ApolloLink> => {
const http = new HttpLink({uri: process.env.GRAPHQL_ENDPOINT, fetch:fetch})
const link = setContext((request, previousContext) => {
if (previousContext
&& previousContext.graphqlContext
&& previousContext.graphqlContext.request
&& previousContext.graphqlContext.request.headers
&& previousContext.graphqlContext.request.headers.authorization) {
const authorization = previousContext.graphqlContext.request.headers.authorization;
return {
headers: {
authorization
}
}
}
else {
return {};
}
}).concat(http);
const wsLink: any = new WebSocketLink(new SubscriptionClient(process.env.REMOTE_GRAPHQL_WS_ENDPOINT, {
reconnect: true,
// There is no way to update connectionParams dynamically without resetting connection
// connectionParams: () => {
// return { Authorization: wsAuthorization }
// }
}, ws));
// Following does not work
const wsLinkContext = setContext((request, previousContext) => {
let authToken = previousContext.graphqlContext.connection && previousContext.graphqlContext.connection.context ? previousContext.graphqlContext.connection.context.Authorization : null
return {
context: {
Authorization: authToken
}
}
}).concat(<any>wsLink);
const url = split(({query}) => {
const {kind, operation} = <any>getMainDefinition(<any>query);
return kind === 'OperationDefinition' && operation === 'subscription'
},
wsLinkContext,
link)
return url;
}
const getSchema = async (): Promise < GraphQLSchema > => {
const link = await getLink();
return makeRemoteExecutableSchema({
schema: await introspectSchema(link),
link,
});
}
const linkSchema = `
extend type UserPayload {
user: User
}
`;
const schema: any = mergeSchemas({
schemas: [linkSchema, getSchema],
});
const server = new GraphQLServer({
schema: schema,
context: req => ({
...req,
})
});
Is there any way for achieving this using graphql-tools? Any help appreciated.
I have one working solution: the idea is to not create one instance of SubscriptionClient for the whole application. Instead, I'm creating the clients for each connection to the proxy server:
server.start({
port: 4000,
subscriptions: {
onConnect: (connectionParams, websocket, context) => {
return {
subscriptionClients: {
messageService: new SubscriptionClient(process.env.MESSAGE_SERVICE_SUBSCRIPTION_URL, {
connectionParams,
reconnect: true,
}, ws)
}
};
},
onDisconnect: async (websocket, context) => {
const params = await context.initPromise;
const { subscriptionClients } = params;
for (const key in subscriptionClients) {
subscriptionClients[key].close();
}
}
}
}, (options) => console.log('Server is running on http://localhost:4000'))
if you would have more remote schemas you would just create more instances of SubscriptionClient in the subscriptionClients map.
To use those clients in the remote schema you need to do two things:
expose them in the context:
const server = new GraphQLServer({
schema,
context: ({ connection }) => {
if (connection && connection.context) {
return connection.context;
}
}
});
use custom link implementation instead of WsLink
(operation, forward) => {
const context = operation.getContext();
const { graphqlContext: { subscriptionClients } } = context;
return subscriptionClients && subscriptionClients[clientName] && subscriptionClients[clientName].request(operation);
};
In this way, the whole connection params will be passed to the remote server.
The whole example can be found here: https://gist.github.com/josephktcheung/cd1b65b321736a520ae9d822ae5a951b
Disclaimer:
The code is not mine, as #josephktcheung outrun me with providing an example. I just helped with it a little. Here is the original discussion: https://github.com/apollographql/graphql-tools/issues/864
This is a working example of remote schema with subscription by webscoket and query and mutation by http. It can be secured by custom headers(params) and shown in this example.
Flow
Client request
-> context is created by reading req or connection(jwt is decoded and create user object in the context)
-> remote schema is executed
-> link is called
-> link is splitted by operation(wsLink for subscription, httpLink for queries and mutations)
-> wsLink or httpLink access to context created above (=graphqlContext)
-> wsLink or httpLink use context to created headers(authorization header with signed jwt in this example) for remote schema.
-> "subscription" or "query or mutation" are forwarded to remote server.
Note
Currently, ContextLink does not have any effect on WebsocketLink. So, instead of concat, we should create raw ApolloLink.
When creating context, checkout connection, not only req. The former will be available if the request is websocket, and it contains meta information user sends, like an auth token.
HttpLink expects global fetch with standard spec. Thus, do not use node-fetch, whose spec is incompatible (especially with typescript). Instead, use cross-fetch.
const wsLink = new ApolloLink(operation => {
// This is your context!
const context = operation.getContext().graphqlContext
// Create a new websocket link per request
return new WebSocketLink({
uri: "<YOUR_URI>",
options: {
reconnect: true,
connectionParams: { // give custom params to your websocket backend (e.g. to handle auth)
headers: {
authorization: jwt.sign(context.user, process.env.SUPER_SECRET),
foo: 'bar'
}
},
},
webSocketImpl: ws,
}).request(operation)
// Instead of using `forward()` of Apollo link, we directly use websocketLink's request method
})
const httpLink = setContext((_graphqlRequest, { graphqlContext }) => {
return {
headers: {
authorization: jwt.sign(graphqlContext.user, process.env.SUPER_SECRET),
},
}
}).concat(new HttpLink({
uri,
fetch,
}))
const link = split(
operation => {
const definition = getMainDefinition(operation.query)
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
)
},
wsLink, // <-- Executed if above function returns true
httpLink, // <-- Executed if above function returns false
)
const schema = await introspectSchema(link)
const executableSchema = makeRemoteExecutableSchema({
schema,
link,
})
const server = new ApolloServer({
schema: mergeSchemas([ executableSchema, /* ...anotherschemas */]),
context: ({ req, connection }) => {
let authorization;
if (req) { // when query or mutation is requested by http
authorization = req.headers.authorization
} else if (connection) { // when subscription is requested by websocket
authorization = connection.context.authorization
}
const token = authorization.replace('Bearer ', '')
return {
user: getUserFromToken(token),
}
},
})

graphql role based authorization

I'm new to GraphQL and going to build a solution using GraphQL.
Everything looks cool but just concerned on how to implement the role based authorization inside GraphQL server (I'm considering using GraphQL.js/ apollo server)
I will have a users table which contains all users. Inside the users table there's a roles field which contains the roles of the particular user. The queries and mutations will be granted based on the roles of the user.
How can I implement this structure?
THANKS!
For apollo server developers, there have generally been 3 ways to implement authorization in Graphql:
Schema-based: Adding a directive to the graphql types and fields you want to protect
Middleware-based: Adding middleware (code that runs before and after your graphql resolvers have executed). This is the approach used by graphql-shield and other authorization libraries built on top of graphql-middleware.
Business logic layer: This is the most primitive but granular approach. Basically, the function that returns data (i.e. a database query, etc) would implement its own permissions/authorization check.
Schema-based
With schema-based authorization, we would define custom schema directives and apply them wherever it is applicable.
Source: https://www.apollographql.com/docs/graphql-tools/schema-directives/
//schema.gql
directive #auth(
requires: Role = ADMIN,
) on OBJECT | FIELD_DEFINITION
enum Role {
ADMIN
REVIEWER
USER
UNKNOWN
}
type User #auth(requires: USER) {
name: String
banned: Boolean #auth(requires: ADMIN)
canPost: Boolean #auth(requires: REVIEWER)
}
// main.js
class AuthDirective extends SchemaDirectiveVisitor {
visitObject(type) {
this.ensureFieldsWrapped(type);
type._requiredAuthRole = this.args.requires;
}
visitFieldDefinition(field, details) {
this.ensureFieldsWrapped(details.objectType);
field._requiredAuthRole = this.args.requires;
}
ensureFieldsWrapped(objectType) {
if (objectType._authFieldsWrapped) return;
objectType._authFieldsWrapped = true;
const fields = objectType.getFields();
Object.keys(fields).forEach(fieldName => {
const field = fields[fieldName];
const { resolve = defaultFieldResolver } = field;
field.resolve = async function (...args) {
// Get the required Role from the field first, falling back
// to the objectType if no Role is required by the field:
const requiredRole =
field._requiredAuthRole ||
objectType._requiredAuthRole;
if (! requiredRole) {
return resolve.apply(this, args);
}
const context = args[2];
const user = await getUser(context.headers.authToken);
if (! user.hasRole(requiredRole)) {
throw new Error("not authorized");
}
return resolve.apply(this, args);
};
});
}
}
const schema = makeExecutableSchema({
typeDefs,
schemaDirectives: {
auth: AuthDirective,
authorized: AuthDirective,
authenticated: AuthDirective
}
});
Middleware-based
With middleware-based authorization, most libraries will intercept the resolver execution. The below example is specific to graphql-shield on apollo-server.
Graphql-shield source: https://github.com/maticzav/graphql-shield
Implementation for apollo-server source: https://github.com/apollographql/apollo-server/pull/1799#issuecomment-456840808
// shield.js
import { shield, rule, and, or } from 'graphql-shield'
const isAdmin = rule()(async (parent, args, ctx, info) => {
return ctx.user.role === 'admin'
})
const isEditor = rule()(async (parent, args, ctx, info) => {
return ctx.user.role === 'editor'
})
const isOwner = rule()(async (parent, args, ctx, info) => {
return ctx.user.items.some(id => id === parent.id)
})
const permissions = shield({
Query: {
users: or(isAdmin, isEditor),
},
Mutation: {
createBlogPost: or(isAdmin, and(isOwner, isEditor)),
},
User: {
secret: isOwner,
},
})
// main.js
const { ApolloServer, makeExecutableSchema } = require('apollo-server');
const { applyMiddleware } = require('graphql-middleware');
const shieldMiddleware = require('shieldMiddleware');
const schema = applyMiddleware(
makeExecutableSchema({ typeDefs: '...', resolvers: {...} }),
shieldMiddleware,
);
const server = new ApolloServer({ schema });
app.listen({ port: 4000 }, () => console.log('Ready!'));
Business logic layer
With business logic layer authorization, we would add permission checks inside our resolver logic. It is the most tedious because we would have to write authorization-checks on every resolver. The link below recommends placing the authorization logic in the business logic layer (i.e. sometimes called 'Models' or 'Application logic' or 'data-returning function').
Source: https://graphql.org/learn/authorization/
Option 1: Auth logic in resolver
// resolvers.js
const Query = {
users: function(root, args, context, info){
if (context.permissions.view_users) {
return ctx.db.query(`SELECT * FROM users`)
}
throw new Error('Not Authorized to view users')
}
}
Option 2 (Recommended): Separating out authorization logic from resolver
// resolver.js
const Authorize = require('authorization.js')
const Query = {
users: function(root, args, context, info){
Authorize.viewUsers(context)
}
}
// authorization.js
const validatePermission = (requiredPermission, context) => {
return context.permissions[requiredPermission] === true
}
const Authorize = {
viewUsers = function(context){
const requiredPermission = 'ALLOW_VIEW_USERS'
if (validatePermission(requiredPermission, context)) {
return context.db.query('SELECT * FROM users')
}
throw new Error('Not Authorized to view users')
},
viewCars = function(context){
const requiredPermission = 'ALLOW_VIEW_CARS';
if (validatePermission(requiredPermission, context)){
return context.db.query('SELECT * FROM cars')
}
throw new Error('Not Authorized to view cars')
}
}
I've recently implemented role based authorisation by using GraphQL Shield, I found that using that package was the simplest way to do it. Otherwise you could add custom schema directives, here's a good article on how to do that: https://dev-blog.apollodata.com/reusable-graphql-schema-directives-131fb3a177d1.
There are a few steps you need to take to setup GraphQL Shield:
1 - Write an authentication function, here's a rough example you'll want to be doing much more than this i.e using JWTs and not passing the id:
export const isAdmin = async ({ id }) => {
try {
const exists = await ctx.db.exists.User({
id: userId,
role: 'ADMIN',
});
return exists
} catch (err) {
console.log(err);
return false
}
}
2 - In the file where you export all of your mutations and queries add the check:
const resolvers = {
...your queries and mutations
}
const permissions = {
Query: {
myQuery: isAdmin
}
}
export default shield(resolvers, permissions);
This will now the isAdmin function every time your Query is requested.
I hope that helps

Apollo GraphQL server; setting context to handle requests triggered by a fired subscription

I understand how to set the context object when creating a GraphQL server e.g.
const app = express();
app.use(GRAPHQL_URL, graphqlExpress({
schema,
context: {
foo: 'bar'
},
}));
so that the context object is passed to my resolvers when handling an incoming request.
However I'm not seeing this context object when the resolvers are triggered by a subscription (i.e. a client subscribes to a GraphQL subscription, and defines the shape of the data to be sent to them when the subscription fires); in that case the context appears to be an empty Object.
Is there way to ensure that my context object is set correctly when resolvers are called following a PubSub.publish() call?
I guess you are using the package subscription-transport-ws. In that case it is possible to add a context value in different execution steps.
See API. Two possible scenarios
If you have some kind of authentication. You could add a viewer in the context at the onConnect execution step. This is done at the first connection to the websocket and wont change until the connection is closed and opened again. See example.
If you want to add a context more dynamically you can add a kind of middleware before the execute step.It could look like this:
const middleware = (args) => new Promise((resolve, reject) => {
const [schema, document, root, context, variables, operation] = args;
context.foo = "bar"; // add something to context
resolve(args);
})
subscriptionServer = SubscriptionServer.create({
schema: executable.schema,
subscribe,
execute: (...args) => middleware(args).then(args => {
return execute(...args);
})
}, {
server: websocketServer,
path: "/graphql",
}, );
Here is my solution:
You can pass the context and do the authentication for graphql subscription(WebSocket )like this:
const server = new ApolloServer({
typeDefs,
resolvers,
context: contextFunction,
introspection: true,
subscriptions: {
onConnect: (
connectionParams: IWebSocketConnectionParams,
webSocket: WebSocket,
connectionContext: ConnectionContext,
) => {
console.log('websocket connect');
console.log('connectionParams: ', connectionParams);
if (connectionParams.token) {
const token: string = validateToken(connectionParams.token);
const userConnector = new UserConnector<IMemoryDB>(memoryDB);
let user: IUser | undefined;
try {
const userType: UserType = UserType[token];
user = userConnector.findUserByUserType(userType);
} catch (error) {
throw error;
}
const context: ISubscriptionContext = {
// pubsub: postgresPubSub,
pubsub,
subscribeUser: user,
userConnector,
locationConnector: new LocationConnector<IMemoryDB>(memoryDB),
};
return context;
}
throw new Error('Missing auth token!');
},
onDisconnect: (webSocket: WebSocket, connectionContext: ConnectionContext) => {
console.log('websocket disconnect');
},
},
});
You can pass the context argument of resolver using pubsub.publish method in your resolver like this:
addTemplate: (
__,
{ templateInput },
{ templateConnector, userConnector, requestingUser }: IAppContext,
): Omit<ICommonResponse, 'payload'> | undefined => {
if (userConnector.isAuthrized(requestingUser)) {
const commonResponse: ICommonResponse = templateConnector.add(templateInput);
if (commonResponse.payload) {
const payload = {
data: commonResponse.payload,
context: {
requestingUser,
},
};
templateConnector.publish(payload);
}
return _.omit(commonResponse, 'payload');
}
},
Now, we can get the http request context and subscription(websocket) context in
your Subscription resolver subscribe method like this:
Subscription: {
templateAdded: {
resolve: (
payload: ISubscriptionPayload<ITemplate, Pick<IAppContext, 'requestingUser'>>,
args: any,
subscriptionContext: ISubscriptionContext,
info: any,
): ITemplate => {
return payload.data;
},
subscribe: withFilter(templateIterator, templateFilter),
},
},
async function templateFilter(
payload?: ISubscriptionPayload<ITemplate, Pick<IAppContext, 'requestingUser'>>,
args?: any,
subscriptionContext?: ISubscriptionContext,
info?: any,
): Promise<boolean> {
console.count('templateFilter');
const NOTIFY: boolean = true;
const DONT_NOTIFY: boolean = false;
if (!payload || !subscriptionContext) {
return DONT_NOTIFY;
}
const { userConnector, locationConnector } = subscriptionContext;
const { data: template, context } = payload;
if (!subscriptionContext.subscribeUser || !context.requestingUser) {
return DONT_NOTIFY;
}
let results: IUser[];
try {
results = await Promise.all([
userConnector.findByEmail(subscriptionContext.subscribeUser.email),
userConnector.findByEmail(context.requestingUser.email),
]);
} catch (error) {
console.error(error);
return DONT_NOTIFY;
}
//...
return true;
}
As you can see, now we get the subscribe users(who establish the WebSocket connection with graphql webserver) and HTTP request user(who send the mutation to graphql webserver) from subscriptionContext and HTTP request context.
Then you can do the rest works if the return value of templateFilter function is truthy, then WebSocket will push message to subscribe user with payload.data, otherwise, it won't.
This templateFilter function will be executed multiple times depending on the count of subscribing users which means it's iterable. Now you get each subscribe user in this function and does your business logic to decide if push WebSocket message to the subscribe users(client-side) or not.
See github example repo
Articles:
GraphQL Subscription part 1
GraphQL Subscription part 2
If you're using Apollo v3, and graphql-ws, here's a docs-inspired way to achieve context resolution:
const wsContext = async (ctx, msg, args) => {
const token = ctx.connectionParams.authorization;
const currentUser = await findUser(token);
if(!currentUser) throw Error("wrong user token");
return { currentUser, foo: 'bar' };
};
useServer(
{
schema,
context: wsContext,
}
wsServer,
);
You could use it like so in your Apollo React client:
import { GraphQLWsLink } from '#apollo/client/link/subscriptions';
import { createClient } from 'graphql-ws';
const wsLink = new GraphQLWsLink(createClient({
url: 'ws://localhost:4000/subscriptions',
connectionParams: {
authorization: user.authToken,
},
}));

Resources