Authentication in graphql - graphql

I'm trying to add authentication in graphql, so only authenticated users can make a request to my server. But when I try to send the id in the Authorization header it doesn't send the information I want. It's a next project with ssr.
import { ApolloClient, InMemoryCache, createHttpLink } from '#apollo/client';
import { setContext } from '#apollo/client/link/context';
import { getAuth } from 'firebase/auth';
const httpLink = createHttpLink({
uri: 'http://localhost:4000/graphql',
});
const authLink = setContext((_, { headers }) => {
const user = getAuth().currentUser;
return {
headers: {
...headers,
authorization: user?.uid ?? "null"
}
}
})
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache()
});
export default client;
I have the id of the user in a redux context, so I tried to use store.getState(), but it didn't work. Also I've tried with firebase getAuth, so I send the uid.

Related

How can you have both Authentication and Subscriptions with Apollo Client

I'm pretty new to apollo and react native. I'm working on a project where I need to use graphql subscriptions and use graphql queries, which need authentication. From the apollo docs I had set-up my client.ts like:
import { ApolloClient, split, HttpLink, InMemoryCache } from '#apollo/client';
import { getMainDefinition } from '#apollo/client/utilities';
import { GraphQLWsLink } from "#apollo/client/link/subscriptions";
import { createClient } from "graphql-ws";
const wslink = new GraphQLWsLink(
createClient({
url: "ws://localhost:4000/subscriptions",
}),
);
const httpLink = new HttpLink({
uri: 'http://localhost:4000/graphql'
});
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
wslink,
httpLink,
);
export const client = new ApolloClient({
cache: new InMemoryCache(),
link: splitLink,
});
but I need to add the authentication. The documents only mention authentication with this type of setup for the subscription aspect of the code:
const wslink = new GraphQLWsLink(
createClient({
url: "ws://localhost:4000/subscriptions",
connectionParams: {
authToken: user.authToken,
},
}),
);
which doesn't help me and gives me an error because user is unknown. The apollo docs give:
import ApolloClient from "apollo-client";
import { HttpLink } from "apollo-link-http";
import { ApolloLink, concat, split } from "apollo-link";
import { InMemoryCache } from "apollo-cache-inmemory";
import { getMainDefinition } from "apollo-utilities";
const httpLink = new HttpLink({ uri: process.env.VUE_APP_API_TARGET });
const authMiddleware = new ApolloLink((operation, forward) => {
// add the authorization to the headers
const token = localStorage.getItem('token');
operation.setContext({
headers: {
authorization: token ? `Bearer ${token}` : "",
},
});
return forward(operation);
});
export const apolloClient = new ApolloClient({
link: concat(authMiddleware, httpLink),
cache: new InMemoryCache(),
});
as an example of how to add in the authentication aspect. What I'm having trouble with is how do you combine the two? Currently, I have:
import { ApolloClient, split, HttpLink, InMemoryCache } from '#apollo/client';
import { getMainDefinition } from '#apollo/client/utilities';
import { ApolloLink, concat} from "apollo-link";
import AsyncStorage from '#react-native-async-storage/async-storage';
import { GraphQLWsLink } from "#apollo/client/link/subscriptions";
import { createClient } from "graphql-ws";
const wslink = new GraphQLWsLink(
createClient({
url: "ws://localhost:4000/subscriptions",
}),
);
const httpLink = new HttpLink({
uri: 'http://localhost:4000/graphql'
});
const authLink = new ApolloLink((operation, forward) => {
const token = /*await*/ AsyncStorage.getItem('token');
operation.setContext({
headers: {
authorization: token ? `Bearer ${token}` : "",
},
});
return forward(operation);
});
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
wslink,
httpLink,
);
export const client = new ApolloClient({
cache: new InMemoryCache(),
link: splitLink,
});
In export const client = new ApolloClient({cache: new InMemoryCache(), link: splitLink, });, I tried changing it to link: concat(authLink, splitLink), but that gives errors
"message": "Type 'ApolloLink' is missing the following properties from type 'ApolloLink': onError, setOnError",
and
"message": "Argument of type 'ApolloLink' is not assignable to parameter of type 'ApolloLink | RequestHandler'.\n Type ... ApolloLink'.\n Types of property 'split' are incompatible.\n
I also tried replacing httpLink in const splitLink... with , but that gave the errors:
"message": "Argument of type 'ApolloLink' is not assignable to parameter of type 'ApolloLink | RequestHandler | undefined'.\n Type 'ApolloLink' is missing the following properties from type 'ApolloLink': onError, setOnError",
and
"message": "Argument of type 'HttpLink' is not assignable to parameter of type 'ApolloLink | RequestHandler'.\n Type 'HttpLink' is not assignable to type 'ApolloLink'.\n Types of property 'split' are incompatible.\n...
Does anyone know how to do this? I would really appreciate any help or advice. Thank you!

Apollo GraphQL: GraphQLWsLink (Subscriptions) Troubles. Cannot get WebSocket implementation to work w/ Next.js

So I have a GraphQL server that I wrote in Go, following this tutorial pretty closely. I have my front-end written as a Next.js application, and I am currently trying to create a client to connect to my server and even following the subscription docs to the T, I cannot seem to get it to work. How is it that the examples provided do not include a webSocketImpl?
If I don't provide a webSocketImpl, I get this:
Error: WebSocket implementation missing; on Node you can `import WebSocket from 'ws';` and pass `webSocketImpl: WebSocket` to `createClient`
So, naturally, I import { WebSocket } from "ws"; , and have:
const wsLink = new GraphQLWsLink(
createClient({
webSocketImpl: WebSocket,
url: "ws://localhost:8080/subscriptions",
})
);
Where I then get:
error - ./node_modules/node-gyp-build/index.js:1:0
Module not found: Can't resolve 'fs'
Here is the full code, basically all I need is to create a ApolloClient and export it for use in my React code.
import { ApolloClient, HttpLink, InMemoryCache, split } from "#apollo/client";
import { GraphQLWsLink } from "#apollo/client/link/subscriptions";
import { createClient } from "graphql-ws";
import { getMainDefinition } from "#apollo/client/utilities";
import { WebSocket } from "ws";
const wsLink = new GraphQLWsLink(
createClient({
webSocketImpl: WebSocket,
url: "ws://localhost:8080/subscriptions",
})
);
const httpLink = new HttpLink({
uri: `http://localhost:8080/query`,
});
const link = split(
({ query }) => {
const def = getMainDefinition(query);
return (
def.kind === "OperationDefinition" && def.operation === "subscription"
);
},
wsLink,
httpLink
);
export const Client = new ApolloClient({
link,
cache: new InMemoryCache(),
});
Am I totally missing something here? Is there not a default WebSocket implementation in my installation? Obviously the "ws" implementation isn't cutting it, probably because fs is not available in-browser?
A major thing I left off here: I was using Next.js. The reason this was occurring was due to SSR. Basically, we need to only generate the WebSocket link if we are in the browser by using `typeof window !== 'undefined'. This is my updated code:
import { ApolloClient, HttpLink, InMemoryCache, split } from "#apollo/client";
import { GraphQLWsLink } from "#apollo/client/link/subscriptions";
import { createClient } from "graphql-ws";
import { getMainDefinition } from "#apollo/client/utilities";
const wsLink =
typeof window !== "undefined"
? new GraphQLWsLink(
createClient({
url: "ws://localhost:8080/subscriptions",
})
)
: null;
const httpLink = new HttpLink({
uri: `http://localhost:8080/query`,
});
const link =
typeof window !== "undefined" && wsLink != null
? split(
({ query }) => {
const def = getMainDefinition(query);
return (
def.kind === "OperationDefinition" &&
def.operation === "subscription"
);
},
wsLink,
httpLink
)
: httpLink;
export const client = new ApolloClient({
link,
cache: new InMemoryCache(),
});
I found a way how it`s work with GraphQL-yoga
It's client :
// import { createServer } from "#graphql-yoga/node";
import { makeExecutableSchema } from "#graphql-tools/schema";
import typeDefs from "#/server/graphql/typeDef/schema.graphql";
import resolvers from "#/server/graphql/resolvers";
import dbInit from "#/lib/dbInit";
import JWT from "jsonwebtoken";
import Cors from "micro-cors";
// const pubsub = new PubSub();
const schema = makeExecutableSchema({
typeDefs,
resolvers,
});
import {
createServer,
createPubSub,
GraphQLYogaError,
} from "#graphql-yoga/node";
import { useResponseCache } from "#envelop/response-cache";
import { WebSocketServer } from "ws"; // yarn add ws
// import ws from 'ws'; yarn add ws#7
// const WebSocketServer = ws.Server;
import { useServer } from "graphql-ws/lib/use/ws";
const pubSub = createPubSub();
const server = createServer({
cors: {
credentials: "same-origin",
origin: ["http://localhost:3000"], // your frontend url.
},
plugins: [
useResponseCache({
includeExtensionMetadata: true,
}),
],
context: async (ctx) => {
let wsServer = null;
wsServer = ctx.res.socket.server.ws ||= new WebSocketServer({
port: 4000,
path: "/api/graphql",
});
wsServer &&= useServer({ schema }, wsServer);
const db = await dbInit();
let { token, customerId, customerExpire } = ctx.req.cookies;
// 1. Find optional visitor id
let id = null;
if (token) {
try {
let obj = JWT.verify(token, "MY_SECRET");
id = obj.id;
} catch (err) {
console.error("error on apollo server", err); // expired token, invalid token
// TODO try apollo-link-error on the client
throw new AuthenticationError(
"Authentication token is invalid, please log in"
);
}
}
return {
...ctx,
userId: id,
customerId,
pubSub,
};
},
schema,
});
export default server;
And client
import { useMemo } from "react";
import {
ApolloClient,
InMemoryCache,
split,
HttpLink,
createHttpLink,
} from "#apollo/client";
import merge from "deepmerge";
import { getMainDefinition } from "apollo-utilities";
import { GraphQLWsLink } from "#apollo/client/link/subscriptions";
import { createClient } from "graphql-ws";
// const link = process.env.SERVER_LINK;
let apolloClient;
//create websocket link
const wsLink =
typeof window !== "undefined"
? new GraphQLWsLink(
createClient({
url: "ws://localhost:4000/api/graphql",
on: {
connected: () => console.log("connected client"),
closed: () => console.log("closed"),
},
})
)
: null;
//create http link
const httplink = new HttpLink({
uri: "http://localhost:3000/api/graphql",
credentials: "same-origin",
});
//Split the link based on graphql operation
const link =
typeof window !== "undefined"
? split(
//only create the split in the browser
// split based on operation type
({ query }) => {
const { kind, operation } = getMainDefinition(query);
return kind === "OperationDefinition" && operation === "subscription";
},
wsLink,
httplink
)
: httplink;
//create apollo client
function createApolloClient() {
return new ApolloClient({
ssrMode: typeof window === "undefined",
link: link,
cache: new InMemoryCache(),
});
}
export function initializeApollo(initialState = null) {
const _apolloClient = apolloClient ?? createApolloClient();
if (initialState) {
// Get existing cache, loaded during client side data fetching
const existingCache = _apolloClient.extract();
// Merge the existing cache into data passed from getStaticProps/getServerSideProps
const data = merge(initialState, existingCache);
// Restore the cache with the merged data
_apolloClient.cache.restore(data);
}
if (typeof window === "undefined") return _apolloClient;
if (!apolloClient) apolloClient = _apolloClient;
return _apolloClient;
}
export function useApollo(initialState) {
const store = useMemo(() => initializeApollo(initialState), [initialState]);
return store;
}

Is there a way to get a hash out of an Apollo request

After trying for hours to try implementing a Hmac token on a react Apollo client request, I just couldn't find a way to retrieve the final request body that is being sent.
Is there a way to get the request body sent to the server before the client sends it or no way at all ? Not being able to get body hash causes for some security concerns for Mitm attacks since using a jwt the way Apollo docs show it doesn't provide with a way of signing the content.
Based on this, I came up with something
import React from "react";
import { render } from "react-dom";
import {
ApolloClient,
ApolloProvider,
HttpLink,
ApolloLink,
InMemoryCache,
concat,
useQuery,
gql,
} from "#apollo/client";
import CryptoJS from "crypto-js";
import { print } from "graphql/language/printer";
const httpLink = new HttpLink({ uri: "http://localhost:3000/dev/query" });
const authMiddleware = new ApolloLink((operation, forward) => {
// add the authorization to the headers
const { query, variables, operationName } = operation,
payload = {
variables,
operationName,
query: print(query),
};
console.log(payload);
// custom headers
const token = getAuthHeader(payload, "key"), // create HMAC here
headers = {
hmac: token,
};
operation.setContext({
headers: {
...headers,
},
});
return forward(operation);
});
const client = new ApolloClient({
cache: new InMemoryCache({
addTypename: false, //quite important
}),
link: concat(authMiddleware, httpLink),
});
function getAuthHeader(payload_string, sk) {
var str = JSON.stringify(payload_string);
console.log(str);
var hmac = CryptoJS.HmacSHA256(str, sk).toString(CryptoJS.enc.Hex);
return hmac;
}
function MakeRequest() {
const request = `
query {
items {
_id
}
}
`;
const { loading, error, data } = useQuery(gql(request));
//Whatever you wanna do with it.
}
function App() {
return (
<div>
<MakeRequest />
</div>
);
}
render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>,
document.getElementById("root")
);
Seems to work correctly.

Apollo client QUERIES not sending headers to server but mutations are fine

I hooked up a front end to a graphql server. Most if not all the mutations are protected while all the queries are not protected. I have an auth system in place where if you log in, you get an access/refresh token which all mutations are required to use. And they do which is great, backend receives the headers and everything!
HOWEVER. There is one query that needs at least the access token to distinguish the current user! BUT the backend does not receive the two headers! I thought that the middlewareLink I created would be for all queries/mutations but I'm wrong and couldn't find any additional resources to help me out.
So here's my setup
apollo-client.js
import { InMemoryCache } from "apollo-cache-inmemory"
import { persistCache } from "apollo-cache-persist"
import { ApolloLink } from "apollo-link"
import { HttpLink } from "apollo-link-http"
import { onError } from "apollo-link-error"
import { setContext } from "apollo-link-context"
if (process.browser) {
try {
persistCache({
cache,
storage: window.localStorage
})
} catch (error) {
console.error("Error restoring Apollo cache", error)
}
}
const httpLink = new HttpLink({
uri: process.env.GRAPHQL_URL || "http://localhost:4000/graphql"
})
const authMiddlewareLink = setContext(() => ({
headers: {
authorization: localStorage.getItem("apollo-token") || null,
"x-refresh-token": localStorage.getItem("refresh-token") || null
}
}))
const afterwareLink = new ApolloLink((operation, forward) =>
forward(operation).map(response => {
const context = operation.getContext()
const {
response: { headers }
} = context
if (headers) {
const token = headers.get("authorization")
const refreshToken = headers.get("x-refresh-token")
if (token) {
localStorage.setItem("apollo-token", token)
}
if (refreshToken) {
localStorage.setItem("refresh-token", refreshToken)
}
}
return response
})
)
const errorLink = onError(({ graphQLErrors, networkError }) => {
...
// really long error link code
...
})
let links = [errorLink, afterwareLink, httpLink]
if (process.browser) {
links = [errorLink, afterwareLink, authMiddlewareLink, httpLink]
}
const link = ApolloLink.from(links)
export default function() {
return {
cache,
defaultHttpLink: false,
link
}
}
Is there a way to target ALL mutations/queries with custom headers not just mutations? Or apply some headers to an individual query since I could probably put that as an app middleware?
edit: Haven't solved the SSR portion of this yet.. will re-edit with the answer once I have.

Accessing Mutation Result in Angular Apollo Graphql

I am new to Graphql and I am using the Apollo client with Angular 7.
I have a mutation in the server that I am using for authentication.This mutation generates returns an access token and a refresh token:
#Injectable({
providedIn: "root"
})
export class LoginTokenAuthGQL extends Apollo.Mutation<
LoginTokenAuth.Mutation,
LoginTokenAuth.Variables
> {
document: any = gql`
mutation loginTokenAuth($input: LoginAuthInput!) {
loginTokenAuth(input: $input) {
accessToken
refreshToken
}
}
`;
}
I am running this mutation in my sign-in component like this:
onLoginSubmit() {
const email = this.loginForm.controls['userEmail'].value;
const password = this.loginForm.controls['userPassword'].value;
console.log('Sending mutation with', email, password);
this.loginGQL.mutate({
input: {
email,
password,
userType: AuthUserType.Crm
}
}).pipe(
map((response) => response.data )
).subscribe(
(output: LoginTokenAuth.Mutation) => {
console.log('Access token', output.loginTokenAuth.accessToken);
console.log('Refresh token', output.loginTokenAuth.refreshToken);
console.log(this.apollo.getClient().cache);
},
((error: any) => {
console.error(error);
})
);
}
Once I get the access token I will need to add it as header on my requests.
From what I read from the Apollo Client all results from queries and mutations are cached locally in the client. But it is not clear to me how can I access them and add it to the apollo-link.
To be more clear I would like to do this in my Graphql module:
const http = httpLink.create({uri: '/graphql'});
const auth = setContext((_, { headers }) => {
// get the authentication token from the cache
const token = ???
if (!token) {
return {};
} else {
return {
headers: headers.append('Authorization', `Bearer ${token}`)
};
}
});
Even official docs of Apollo Client suggest you store this token as usually - to localStorage.
import { ApolloClient } from 'apollo-client';
import { createHttpLink } from 'apollo-link-http';
import { setContext } from 'apollo-link-context';
import { InMemoryCache } from 'apollo-cache-inmemory';
const httpLink = createHttpLink({
uri: '/graphql',
});
const authLink = setContext((_, { headers }) => {
// get the authentication token from local storage if it exists
const token = localStorage.getItem('token');
// return the headers to the context so httpLink can read them
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : "",
}
}
});
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache()
});

Resources