How to set Auth token cookie from GraphQL Mutation with Apollo - reactjs

I'm using GraphQLServer from graphql-yoga to handle requests. My back-end is able to communicate with my React front-end at this point and I can make graphql queries and get the response just fine.
I was recently informed that I should be setting a cookie with the token, rather than returning it in the mutation response. So I'm trying to switch over but the cookie isn't being set by the mutation.
server.js (node)
import { GraphQLServer, PubSub } from 'graphql-yoga';
import {resolvers, fragmentReplacements} from './resolvers/index'
import prisma from './prisma'
const pubsub = new PubSub()
export default new GraphQLServer({
typeDefs: './src/schema.graphql',
resolvers,
context(request) {
return {
pubsub,
prisma,
request, //fragmentReplacements[], request, response
}
},
fragmentReplacements
});
Mutation.js (node)
export default {
async createUser(parent, args, {prisma, request}, info) {
const lastActive = new Date().toISOString()
const user = await prisma.mutation.createUser({ data: {...args.data, lastActive }})
const token = generateToken(user.id)
const options = {
maxAge: 1000 * 60 * 60 * 24, //expires in a day
// httpOnly: true, // cookie is only accessible by the server
// secure: process.env.NODE_ENV === 'prod', // only transferred over https
// sameSite: true, // only sent for requests to the same FQDN as the domain in the cookie
}
const cookie = request.response.cookie('token', token, options)
console.log(cookie)
return {user}
},
// more mutations...
console.log(cookie) outputs with the cookie attached
index.js (react)
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './components/App';
import ApolloClient, { InMemoryCache, create } from 'apollo-boost';
import {ApolloProvider} from 'react-apollo'
const client = new ApolloClient({
uri: 'http://localhost:4000',
cache: new InMemoryCache(),
credentials: 'include',
request: async operation => {
operation.setContext({
fetchOptions: {
credentials: 'same-origin'
}
})
},
})
ReactDOM.render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>,
document.getElementById('root'));
So my questions are:
Is there a better way to do authentication with GraphQL, or is setting the token with a cookie in the auth mutation suitable?
Assuming it's a decent approach, how can I set the cookie from the mutation?
Thanks for your time!

You need to include the credentials in the ApolloClient
fetchOptions: {
credentials: 'include'
}

Related

Subscribe only available for AWS AppSync endpoint

I get issues when using #apollo/client: 3.5.10, aws-appsync:4.1.5.
There is my config
import { ApolloClient, ApolloLink, createHttpLink, InMemoryCache } from '#apollo/client';
import { createAuthLink } from 'aws-appsync-auth-link';
import { createSubscriptionHandshakeLink } from 'aws-appsync-subscription-link';
// Config
import { AWS_APPSYNC } from '../config';
const { graphqlEndpoint, region, apiKey } = AWS_APPSYNC;
const auth = {
type: AWS_APPSYNC.authenticationType,
apiKey: apiKey,
};
const httpLink = createHttpLink({ uri: graphqlEndpoint });
const link = ApolloLink.from([
// #ts-ignore
createAuthLink({ graphqlEndpoint, region, auth }),
// #ts-ignore
createSubscriptionHandshakeLink({ graphqlEndpoint, region, auth }, httpLink),
]);
export const client = new ApolloClient({
link,
cache: new InMemoryCache(),
});
and I am using
const {
data: subscription_message_data,
loading: subscription_message_loading,
error: subscription_message_error
} = useSubscription(
SUBSCRIPTION_NEW_MESSAGE, {
variables: { conversationId: conversationId }
});
But I got an error form useSubscription is: "Subscribe only available for AWS AppSync endpoint"
Does anyone have experience with this issue?
Had the exact same issue, and was baffled by this error message.
At the end the issue turned out to be the incorrect property name for the url used in the createAuthLink and createSubscriptionHandshakeLink input objects.
This was the fix that worked for me:
const link = ApolloLink.from([
createAuthLink({ url: graphQLEndpoint, region, auth }),
createSubscriptionHandshakeLink({ url: graphQLEndpoint, region, auth }, httpLink),
]);

Access to fetch at 'http://localhost:6969/graphql' from origin 'http://localhost:3000' has been blocked by CORS policy:

This is reamining part of error -> No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
I am learning Graphql with React js with help of Apollo Client ,
This is my Server.js Code :-
const express = require("express");
const app = express();
const PORT = 6969;
const userData = require("./MOCK_DATA.json");
const schema = require("./Schemas/index.js")
const graphql = require("graphql");
const {
GraphQLObjectType,
GraphQLSchema,
GraphQLInt,
GraphQLString,
GraphQLList
} = graphql
const {graphqlHTTP} = require('express-graphql')
app.use('/graphql', graphqlHTTP({
schema,
graphiql: true
}))
app.listen(PORT, () => {
console.log("Server Running");
})
This is my App.js File To show Fetched Data :-
import React from 'react';
import './App.css';
import { ApolloClient, InMemoryCache, ApolloProvider, HttpLink, from } from '#apollo/client'
import {onError} from '#apollo/client/link/error';
import GetUser from './Components/GetUser';
const errorLink = onError(({ graphqlErrors, networkError }) => {
if (graphqlErrors) {
graphqlErrors.map(({ message, location, path }) => {
alert(`Graphql error ${message}`);
});
}
});
const link = from([
errorLink,
new HttpLink({ uri: "http://localhost:6969/graphql" }),
]);
const client = new ApolloClient({
cache: new InMemoryCache(),
link: link,
});
function App() {
return (
<ApolloProvider client={client }>
<GetUser/>
</ApolloProvider>
);
}
export default App;
As I know, you should give access to the http://localhost:3000 on your backend file.
However, here is the more accurate answer for your question.
Also check the followings out :
CORS issue with using localhost:3000 to access graphql API at a different URL
CORS error: Access to fetch backend to frontend, Graphql (Nodejs, Reactjs)

Add default axios header after login nextjs

I'm using Next.js for my app, and currently have an API route that sets a JWT as a cookie. Throughout the app, I'm using Axios to fetch all of my data from external APIs, and after a user logs in I need to set that cookie as a default request header on every API call to make sure that a user has been authenticated. The basic flow is like this:
The login form sends a post request to my API route at /api/auth/login, passing the username and password and returning the JWT, and setting it as a cookie. Once the idToken cookie has been set I need to add that as an authentication header to every API request within my Axios instance, seen as adapter here. How can I go about getting this done?
My handle login function:
const handleLogin = async (values: ValuesProps) => {
const response = await axios.post('/api/auth/login', values);
if (response.status !== 200) {
throw new Error(response.statusText);
}
};
Which speaks to api/auth/login:
import { NextApiRequest, NextApiResponse } from 'next';
import { setCookie, parseCookies } from 'nookies';
import { adapter } from 'utils/api/config';
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method !== 'POST') {
res.status(500).json('Only POST requests allowed at this route.');
} else {
const { data } = await adapter.post(AUTH.login, JSON.stringify(req.body));
const cookies = parseCookies();
setCookie({ res }, 'idToken', data.token, {
maxAge: 30 * 24 * 60 * 60,
path: '/',
});
api.defaults.headers.Authorization = `Bearer ${cookies['idToken']}`
res.status(200).json(data);
}
};
export default handler;
As you see here I tried adding adapter.defaults.headers.Authorization as a global default, but I'm not seeing it in my request headers. What's the best way to go about setting this globally?
You could use axios.create. It is a factory that creates new instances of axios. So you write a function
import axios from "axios";
export const axiosInstance = async () =>{
// you need to be careful in next.js for adding cookies.
// You could be on the server or on client. this code will work for client assuming that you will be using on client side
// I belive you are using `parser` to get cookies. get the token
const yourToken="whatever"
const axiosClient = axios.create({
baseURL: 'baseUrlHere',
timeout: 1000,
headers: {
'Accept': 'application/vnd.GitHub.v3+json',
// this is how u set in your code
'Authorization': `Bearer ${cookies['idToken']}`
}
});
return axiosClient
}
Then import this in anywhere you want to use:
const {data}=await axiosInstance().post("/auth")
Technically this should work
You can set default header to all axios request by command:
const token = getCookie('token')
axios.defaults.headers.common["idToken"] = token

React in heroku does not recognize the environment

I am creating an application in react with graphql for the API and rails as the backend. I have just configured the environment variables so that depending on whether the application is running in production or in development the url that is consumed in the front changes.
But when I deploy in heroku and check the console in the browser, the url of the api is still the development one http://localhost:3000. I don't know why React is not recognizing the production environment in heroku.
I just created a file constant.js:
const prod = {
url: {
API_URL: 'https://hackathon-one-api.herokuapp.com/graphql'
}
}
const dev = {
url: {
API_URL: 'http://localhost:3000/graphql'
}
}
console.log(process.env.NODE_ENV)
export const config = process.env.NODE_ENV === 'development' ? dev : prod;
And in my index.js (where is imported):
// Environments variables
import { config } from '../src/constant';
// 1
import { ApolloProvider } from 'react-apollo';
import { ApolloClient } from 'apollo-client';
import { createHttpLink } from 'apollo-link-http';
import { setContext } from 'apollo-link-context';
import { InMemoryCache } from 'apollo-cache-inmemory';
// 2
const httpLink = createHttpLink({
uri: config.url.API_URL //here!
});
// 3
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}` : "",
}
}
});
// 4
export const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache()
})

How can I setup an Apollo client in React for both upload and subscriptions?

I would like to set up a graphql client with React for both uploading file and handle subscriptions from a graphql server.
The file upload and the other queries work well. The problem is with subscriptions. I get in the browser console the following error:
WebSocket connection to 'ws://localhost:3001/subscriptions' failed: Connection closed before receiving a handshake response
I have used apollo-upload-client for file upload and apollo-link-ws for subscriptions.
I can see that subscriptions-transport-ws suggests using createNetworkInterface and addGraphQLSubscriptions but this approach is not compatible with apollo-upload-client that only supports createUploadLink.
I'm stuck. Please help.
I setup my client like this:
import React from 'react';
import ApolloClient from 'apollo-client';
import { ApolloProvider } from 'react-apollo';
import { createUploadLink } from 'apollo-upload-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { ApolloLink, Observable, split } from 'apollo-link';
import { WebSocketLink } from 'apollo-link-ws';
import { getMainDefinition } from 'apollo-utilities';
const cache = new InMemoryCache();
const request = async (operation) => {
const token = localStorage.getItem('token');
operation.setContext({
headers: {
authorization: token ? `Bearer ${token}` : '',
},
});
};
const httpLink = createUploadLink({ uri: 'http://localhost:3001/graphql' });
// Create a WebSocket link:
const wsLink = new WebSocketLink({
uri: 'ws://localhost:3001/subscriptions',
options: {
reconnect: true
},
});
// using the ability to split links, you can send data to each link
// depending on what kind of operation is being sent
const link = split(
// split based on operation type
({ query }) => {
const { kind, operation } = getMainDefinition(query);
return kind === 'OperationDefinition' && operation === 'subscription';
},
wsLink,
httpLink,
);
const requestLink = new ApolloLink((operation, forward) =>
new Observable((observer) => {
let handle;
Promise.resolve(operation)
.then(oper => request(oper))
.then(() => {
handle = forward(operation).subscribe({
next: observer.next.bind(observer),
error: observer.error.bind(observer),
complete: observer.complete.bind(observer),
});
})
.catch(observer.error.bind(observer));
return () => {
if (handle) handle.unsubscribe();
};
}));
const apolloClient = new ApolloClient({
link: ApolloLink.from([
requestLink,
link,
]),
cache,
});
export const withApolloClient = App => (
<ApolloProvider client={apolloClient}>
<App client={apolloClient} />
</ApolloProvider>
);
export default apolloClient;
I am using a similar config but instead of importing WebSocketLink from apollo-link-ws I imported it from #apollo/client.
With that setup i had both the subscription and upload working.
import { WebSocketLink } from "#apollo/client/link/ws";
I would suggest to use graphql-server-express like this

Resources