Setting up a secure (wss) websockets server with Apollo and GraphQL - websocket

I am using the following pattern to set up a websockets connection for Apollo/GraphQL subscriptions:
import express from 'express';
import {
graphqlExpress,
graphiqlExpress,
} from 'apollo-server-express';
import bodyParser from 'body-parser';
import cors from 'cors';
import { execute, subscribe } from 'graphql';
import { createServer } from 'http';
import { SubscriptionServer } from 'subscriptions-transport-ws';
import { schema } from './src/schema';
const PORT = 3000;
const server = express();
server.use('*', cors({ origin: `http://localhost:${PORT}` }));
server.use('/graphql', bodyParser.json(), graphqlExpress({
schema
}));
server.use('/graphiql', graphiqlExpress({
endpointURL: '/graphql',
subscriptionsEndpoint: `ws://localhost:${PORT}/subscriptions`
}));
// Wrap the Express server
const ws = createServer(server);
ws.listen(PORT, () => {
console.log(`Apollo Server is now running on http://localhost:${PORT}`);
// Set up the WebSocket for handling GraphQL subscriptions
new SubscriptionServer({
execute,
subscribe,
schema
}, {
server: ws,
path: '/subscriptions',
});
});
How can I change the connection to use the secure websockets protocol? Simply changing 'ws://' to 'wss://' does not work.

I made a sample for graphql-subscription websocket secure connection.
Here is link: https://github.com/mrdulin/apollo-server-express-starter/tree/master/src/subscription/wss-with-nodejs-server
The key point is you need tls or ssl credentials.
For development, you can generate self-signed credentials using openssl.

You need to use the https instead of the http package.
You also would need to have a certificate. Eather a self-signed or from a certification authority.
It could look like this:
import { createServer } from 'https';
...
// load / get certificate
...
const wss = createServer(sslCredentails, server);
wss.listen(PORT, () => {
console.log(`Apollo Server is now running on https://localhost:${PORT}`);
// Set up the WebSocket for handling GraphQL subscriptions
new SubscriptionServer({
execute,
subscribe,
schema
}, {
server: wss,
path: '/subscriptions',
});
});

Related

Getting "connect_error due to xhr poll error" while connecting socket server

I am trying to connect socket.io client which inside react app to the socket.io server but i am getting xhr poll error. I am unable to figure out what is going wrong? client & server code is as follow:
import io from 'socket.io-client';
const socket = io("ws://loacalhost:5000");
socket.on("connect_error", (err) => {
console.log(`connect_error due to ${err.message}`);
});
const express = require('express');
const app = express();
app.use(require('cors')());
const http = require('http');
const server = http.createServer(app);
const io = require("socket.io")(server, {
rejectUnauthorized: false,
cors: {
origin: "http://localhost:3000",
methods: ["GET", "POST"]
}
})
io.on("connection", (socket) => {
console.log(socket.id)
})
server.listen(5000, () => {
console.log('Server is listening on Port 5000');
});
Everything is ok in server file as well as everything is ok in client file except there is syntax mistake in client code i.e.
const socket = io("ws://loacalhost:5000");
replaced with:
const socket = io("ws://localhost:5000");
then it is worked fine, successfully connected to server
Your localhost spelling is incorrect.
const socket = io("ws://loacalhost:5000");
Replaced with
const socket = io("ws://localhost:5000");

Can't emit from server to client and from client to server [Socket.io]

I'm creating my product and stuck with this problem. One day I setuped socket.io and everything worked well. On the next day I migrate my server and client from http to https. After the migration client side and server side still connected, but I can't emit from client side to server and from server to client.
Server side
I have my ssl certificate inside ./security/cert.key and ./security/cert.pem they are loading correctly. My server running on https://localhost:5000
import fs from "fs";
import https from "https";
import socketio from "socket.io";
import express from "express";
// HTTPS optiosn
const httpsOptions = {
key: fs.readFileSync("./security/cert.key"),
cert: fs.readFileSync("./security/cert.pem"),
};
// Setup express and https server
const app = express();
const server = https.createServer(httpsOptions, app);
// Setup socket io
const io = socketio.listen(server, {
origins: "https://localhost:3000",
transports: ["websocket"],
});
server.listen(5000, () => {
console.log(`server listening on https://localhost:5000`);
});
io.listen(server);
io.on("connection", (socket) => {
console.log("new socket connected!");
console.log(`data = ${socket.handshake.query.data}`);
socket.emit("some-event");
socket.on("some-event-2", () => console.log("some-event-2 happened!"));
});
Client Side
My example react component. My react app is running on https://localhost:3000. HTTPS is connected and working well.
import React from "react";
import io from "socket.io-client";
const Sandbox: React.FC = () => {
const query = {
"data": 123,
};
const socket = io.connect("https://localhost:5000", {
secure: true,
query,
transports: ["websocket"],
});
socket.on("connect", () => console.log("connect!"));
socket.on("some-event", () => console.log("some event happened"));
socket.emit("some-event-2");
return <React.Fragment />;
};
export default Sandbox;
And now the problem. On client side in console I should see connect! and some event happened
And on server side I should see the messages new socket connected! and data = 123, some-event-2 happened!. But instead my client side console is completely clear
And server side console have only a few logs, but dont contains emit logs
What should I do? Maybe I'm incorrectly using socket.io with https?
I fixed my error.
The problem was that I was firstly create https server and after that only call .listen() on it. listen() - is not a void, it's return another server obj. You need to pass the result of .listen() function inside your io.listen()
// Don't do that❌
var server = https.createServer(options, app);
server.listen(5000);
io.listen(server);
// Do that✅
var server = https.createServer(options, app).listen(5000);
io.listen(server);

How to use Apollo Client with AppSync?

AppSync uses MQTT over WebSockets for its subscription, yet Apollo uses WebSockets. Neither Subscription component or subscribeForMore in Query component works for me when using apollo with AppSync.
One AppSync feature that generated a lot of buzz is its emphasis on
real-time data. Under the hood, AppSync’s real-time feature is powered
by GraphQL subscriptions. While Apollo bases its subscriptions on
WebSockets via subscriptions-transport-ws, subscriptions in GraphQL
are actually flexible enough for you to base them on another messaging
technology. Instead of WebSockets, AppSync’s subscriptions use MQTT as
the transport layer.
Is there any way to make use of AppSync while still using Apollo?
Ok, here is how it worked for me. You'll need to use aws-appsync SDK (https://github.com/awslabs/aws-mobile-appsync-sdk-js) to use Apollo with AppSync. Didn't have to make any other change to make subscription work with AppSync.
Configure ApolloProvider and client:
// App.js
import React from 'react';
import { Platform, StatusBar, StyleSheet, View } from 'react-native';
import { AppLoading, Asset, Font, Icon } from 'expo';
import AWSAppSyncClient from 'aws-appsync' // <--------- use this instead of Apollo Client
import {ApolloProvider} from 'react-apollo'
import { Rehydrated } from 'aws-appsync-react' // <--------- Rehydrated is required to work with Apollo
import config from './aws-exports'
import { SERVER_ENDPOINT, CHAIN_ID } from 'react-native-dotenv'
import AppNavigator from './navigation/AppNavigator';
const client = new AWSAppSyncClient({
url: config.aws_appsync_graphqlEndpoint,
region: config.aws_appsync_region,
auth: {
type: config.aws_appsync_authenticationType,
apiKey: config.aws_appsync_apiKey,
// jwtToken: async () => token, // Required when you use Cognito UserPools OR OpenID Connect. token object is obtained previously
}
})
export default class App extends React.Component {
render() {
return <ApolloProvider client={client}>
<Rehydrated>
<View style={styles.container}>
<AppNavigator />
</View>
</Rehydrated>
</ApolloProvider>
}
Here is how the subscription in a component looks like:
<Subscription subscription={gql(onCreateBlog)}>
{({data, loading})=>{
return <Text>New Item: {JSON.stringify(data)}</Text>
}}
</Subscription>
Just to add a note about the authentication as it took me a while to work this out:
If the authenticationType is "API_KEY" then you have to pass the apiKey as shown in #C.Lee's answer.
auth: {
type: config.aws_appsync_authenticationType,
apiKey: config.aws_appsync_apiKey,
}
If the authenticationType is "AMAZON_COGNITO_USER_POOLS" then you need the jwkToken, and
if you're using Amplify you can do this as
auth: {
type: config.aws_appsync_authenticationType,
jwtToken: async () => {
const session = await Auth.currentSession();
return session.getIdToken().getJwtToken();
}
}
But if your authenticationType is "AWS_IAM" then you need the following:
auth: {
type: AUTH_TYPE.AWS_IAM,
credentials: () => Auth.currentCredentials()
}

Apollo GraphQL: How to Set Up Secure Websockets?

I'm setting up my dev system to use https, and Chrome is complaining about my websocket not begin secure:
VM4965:161 Mixed Content: The page at 'https://mywebsite.io/' was
loaded over HTTPS, but attempted to connect to the insecure WebSocket
endpoint 'ws://mywebsite.io:4000/subscriptions'. This request has
been blocked; this endpoint must be available over WSS.
Here's my current server-side setup for WS, based on the Apollo docs:
const localHostString = 'mywebsite.io';
const pubsub = new PubSub();
// additional context you use for your resolvers, if any
const context = {connectors: connectors};
//SET UP APOLLO QUERY / MUTATIONS / PUBSUB
//start a graphql server with Express handling a possible Meteor current user
createApolloServer({
schema,
context
});
const METEOR_PORT = 3000;
const GRAPHQL_PORT = 4000;
const server = express();
server.use('*', cors({ origin: `https://${localHostString}:${METEOR_PORT}` }));
server.use('/graphql', bodyParser.json(), graphqlExpress({
schema,
context
}));
server.use('/graphiql', graphiqlExpress({
endpointURL: '/graphql',
subscriptionsEndpoint: `ws://${localHostString}:${GRAPHQL_PORT}/subscriptions`
}));
// Wrap the Express server
const ws = createServer(server);
ws.listen(GRAPHQL_PORT, () => {
console.log(`GraphQL Server is now running on http://${localHostString}:${GRAPHQL_PORT}`);
console.log(`GraphiQL available at http://${localHostString}:${GRAPHQL_PORT}/graphiql`);
// Set up the WebSocket for handling GraphQL subscriptions
new SubscriptionServer({
execute,
subscribe,
schema
}, {
server: ws,
path: '/subscriptions',
});
});
How can I update this so as to use WSS rather than WS websockets?
Thanks in advance to all for any info.
It looks like you're doing
subscriptionsEndpoint: `ws://${localHostString}:${GRAPHQL_PORT}/subscriptions
Maybe instead, change ws to wss. ie:
subscriptionsEndpoint: `wss://${localHostString}:${GRAPHQL_PORT}/subscriptions

Apollo GraphQL: Setting Port for HTTPBatchedNetworkInterface?

I'm trying to connect to a local dev environment via an IP address. I'm getting an error because HTTPBatchedNetworkInterface shows:
_uri: "http://10.0.1.10/graphql"
...when it needs to be:
"http://10.0.1.10:3000/graphql"
Here's my server-side setup code:
const localHostString = '10.0.1.10';
const METEOR_PORT = 3000;
const GRAPHQL_PORT = 4000;
const server = express();
server.use('*', cors({ origin: `http://${localHostString}:${METEOR_PORT}` }));
server.use('/graphql', bodyParser.json(), graphqlExpress({
schema,
context
}));
server.use('/graphiql', graphiqlExpress({
endpointURL: '/graphql',
subscriptionsEndpoint: `ws://${localHostString}:${GRAPHQL_PORT}/subscriptions`
}));
// Wrap the Express server
const ws = createServer(server);
ws.listen(GRAPHQL_PORT, () => {
console.log(`GraphQL Server is now running on http://${localHostString}:${GRAPHQL_PORT}`);
console.log(`GraphiQL available at http://${localHostString}:${GRAPHQL_PORT}/graphiql`);
// Set up the WebSocket for handling GraphQL subscriptions
new SubscriptionServer({
execute,
subscribe,
schema
}, {
server: ws,
path: '/subscriptions',
});
});
What is the correct way to get the port number into HTTPBatchedNetworkInterface._uri?
Thanks in advance to all for any info.
Fixed. My framework is Meteor and I had to set ROOT_URL = 10.0.1.10:3000/.

Resources