I am outside a React component but need to update my auth context 'AuthContext' to delete the existing user to log the user out entirely from all logged in pages
I'm able to dispatch a redux action using Redux. How can I achieve the same using Context?
Below are my code... the logOutAll function is not working because useContext can't be used outside of a React component or function
Appreciate advise from those who have successfully implemented this. Thanks
import { useContext } from "react"
import axios from "axios"
import { AuthContext } from "../context/authContext"
const api = axios.create({
baseURL: "http://localhost:5000/api",
headers: {
"Content-Type": "application/json",
},
})
/**
intercept any error responses from the api
and check if the token is no longer valid.
ie. Token has expired or user is no longer
authenticated.
logout the user if the token has expired
**/
// const logOutAll = () => {
// const { authStatus, setAuthStatus } = useContext(AuthContext)
// console.log(authStatus)
// setAuthStatus({
// ...authStatus,
// isAuthenticated: false,
// isLoading: false,
// user: null,
// })
// }
api.interceptors.response.use(
res => res,
err => {
if (err.response.status === 401) {
console.log("log out all")
logOutAll() //! CANNOT WORK BECAUSE UseContext cannot be called outside React function
or component
}
return Promise.reject(err)
}
)
export default api
I set up my axios interceptors in my app.js file instead and it seems to be working fine
const App = () => {
const { authStatus, setAuthStatus } = useContext(AuthContext)
useEffect(() => {
const checkLoggedIn = async () => {
let token = localStorage.getItem("#token")
if (token === null) {
localStorage.setItem("#token", "")
token = ""
}
const res = await api.get("/users/me", {
headers: {
authorization: token,
},
})
//TODO
// const token = localStorage.getItem("#token")
// console.log("TOKEN", token)
// if (token) {
// setAuthToken(token)
// }
// const res = await api.get("http://localhost:5000/api/users/me")
console.log(res)
setAuthStatus(authStatus => ({
...authStatus,
isAuthenticated: true,
isLoading: false,
user: res.data,
}))
}
checkLoggedIn()
}, [setAuthStatus])
api.interceptors.response.use(
res => res,
err => {
if (err.response.status === 401) {
setAuthStatus({
...authStatus,
isAuthenticated: false,
isLoading: false,
user: null,
})
}
return Promise.reject(err)
}
)
Related
I was working on a project using Firebase cloud messaging react. I was sending this to my server, but it doesn't work. Surely I have tried, but I don't know what's wrong again.
Below is the code.
Here it sends a POST request to Firebase, and it should send a notification to the user.
async function sendNotification(id, userMessage) {
const headers = {
'Authorization': `key=${code}`,
'Content-Type': 'application/json'
}
const message = {
'to': `${id}`,
'content_available': true,
'apns_priority': 5,
'notification': {
body: `${userMessage}`
},
const url = 'https://fcm.googleapis.com/fcm/send'
//console.log(code)
await axios.post(url, message, {
headers: headers
})
}
const sendMessageToServer = async (e) => {
//e.preventDefault();
toggle()
const docRe = doc(database, "help", mailer);
const data = {
email: user.email,
user: newMessage,
}
//console.log(data, 'not clear')
setNewMessage('')
//console.log(data, newMessage, 'cleared')
setShow(false)
if(newMessage === '') {
}
else {
const docRef = doc(database, "users", mailer);
await updateDoc(docRe, {
msg: arrayUnion(data)
})
.then(() => {
async function p() {
const id = await getDoc(docRef)
//console.log(id.data())
sendNotification(id.data().notice, `Admin : ${data.user}`)
}
p()
})
}
Sometimes it sends to my localhost because I tested there, but it doesn't work on my Netlify app. Secondly, I noticed that it keeps generating the same token for each user, but that's not the issue, but if you can help in both I would be grateful.
export default function Dashboard() {
async function callToken() {
await getToken(messaging, {vapidKey: process.env.NOTIFICATION})
.then((code) => {
//console.log(code)
async function docRef() {
const dc = doc(database, "users", auth.currentUser.email);
await updateDoc(dc, {
notice: code
});
}
docRef()
})
}
async function requestPermission() {
await Notification.requestPermission()
.then((permission) => {
if (permission === 'granted') {
console.log('Notification permission granted.')
callToken()
}
})
}
const goTo = useNavigate();
useEffect(() => {
onAuthStateChanged(auth, (data) => {
if(!data) {
goTo('/login')
}
else {
currentBalance();
requestPermission()
}
})
})
}
Please know I imported all required modules.
I want to use my refresh token to get a new access token in a react application. I send a request to an API to get data about books. if I get 401 or 500 error I want to send a request to another API with my refresh token to get a new access token to send to first API to get data about books.
here I use 2 useEffect first for getting books data and second to get new access token . also I have a setInterval to run first useEffect which gets books data. If I change access token in localstorage and crash it deliberately to get 401 error manually I want that new access token that comes from refresh token makes access token in localstorage correct again so stop page from crashing.so my problem is 2 things: first I dont know what to do with my new accesstoken . second is When I change accesstoken in localStorage to manually get 401 error , if I refresh the page I want to my localStorage set my new access token so page does not crash.
here is my useContext and my component which handles these two useEffects:
here is my useContext hook:
import React from "react";
import { useState } from "react";
const AuthContext = React.createContext({
token: "",
refreshToken: "",
isLoggedIn: false,
login: () => {},
logout: () => {},
booksData: [],
});
export const AuthContextProvider = (props) => {
let initialToken = localStorage.getItem("token");
let initialRefreshToken = localStorage.getItem("refresh-token");
const [token, setToken] = useState(initialToken);
const [refreshToken, setRefreshToken] = useState(initialRefreshToken);
const isUserLoggedIn = !!token;
const logoutHandler = () => {
setToken(null);
localStorage.removeItem("token");
localStorage.removeItem("books");
localStorage.removeItem("refresh-token")};
const loginHandler = (token, refreshToken) => {
setToken(token);
setRefreshToken(refreshToken);
localStorage.setItem("token", token);
localStorage.setItem("refresh-token", refreshToken);
};
const contextValue = {
token,
isLoggedIn: isUserLoggedIn,
refreshToken,
login: loginHandler,
logout: logoutHandler,
};
return (
<AuthContext.Provider value={contextValue}>
{props.children}
</AuthContext.Provider>
);
};
export default AuthContext;
and here is my component:
const Books = () => {
const ctx = useContext(AuthContext);
const [books, setBooks] = useState([]);
const [reqCounter, setReqCounter] = useState(0);
const [tokenError, setTokenError] = useState(false);
useEffect(() => {
const fetchData = async () => {
let response = await fetch("some API endpoint", {
method: "GET",
headers: {
Authorization: `Bearer ${ctx.token}`,
},
});
try {
const data = await response.json();
if (response.status === 200) {
setBooks(data.books);
} else if (response.status === 404) {
setError("No page found");
} else if (response.status === 403) {
setError("You dont have accsess to this page");
}
} catch (error) {
setTokenError(true);
}
};
fetchData();
}, [ctx.token, reqCounter, ctx]); // Is my dependencies right??
setInterval(() => {
setReqCounter(reqCounter + 1);
}, 5000);
useEffect(() => {
const refresh = async () => {
const response = await fetch("some API", {
method: "POST",
body: JSON.stringify({
refresh_token: ctx.refreshToken,
}),
headers: {
"Content-Type": "application/json",
},
});
const data = await response.json();
if (response.ok) {
// Dont Know what should I write here!
}
};
refresh();
}, [tokenError]); // Is my dependencies right??
const content = books.map((item) => (
<BookItem
title={item.name}
year={item.publish_date}
pages={item.pages}
author={item.Author}
img={item.thumbnail}
key={item.name}
/>
));
return (
<section className={classes.bookPage}>
{!error && books.length !== 0 && (
<ul className={`list ${classes.booksList}`}>{content}</ul>
)}
{error && <h2 className={classes.error}>{error}</h2>}
{isLoading && <PulseLoader color="#f53e3e" className={classes.spinner} />}
</section>
);
};
export default Books;
Suggestions
Ideally Handle fetch with token and token refresh in one place, something like HttpContext
but to check you can start with existing authcontext
you can refresh token on regular intervals
or when the call in unauthorized
issues:
when token expires, some call will fail, which needs to be made again with a new token
When token is refreshed at regular interval, if the old token is invalidated, some call in the queue with older token could fail
pseudo code
in AuthContext
const fetchData = async (link) => {
try {
let response = await fetch(link, {
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
},
});
} catch(error) {
// check status and attempt refresh
// but existing calls will old token will fail,
// can will cause multiple refresh token to be called
}
}
//or refresh token on regular interval
useEffect(() => {
const timerId = setInterval(() => {
// refresh token and set token
// The problems is the moment the token is refreshed, the old token might get invalidated and some calls might fail
}, tokenRefershTimeInMilliSec)
return () => {
clearInterval(timerId)
}
}, [])
...
const contextValue = {
token,
isLoggedIn: isUserLoggedIn,
refreshToken,
login: loginHandler,
logout: logoutHandler,
get: fetchData
};
return <AuthContext.Provider value={contextValue} {...props}> // pass all props down
or use a http context to seperate concerns
const initalValue = // some value
const HttpContext = React.createContext(initalValue);
const initialToken = // from localstorage
const HttpContextProvider = (props) => {
const [token, setToken] = useState(initialToken)
const fetchData = async (link) => {
try {
let response = await fetch(link, {
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
},
});
} catch(error) {
// check status and attempt refresh
// but existing calls will old token will fail,
// can will cause multiple refresh token to be called
}
}
const value = useMemo(() => {
return {
get: fetchData,
// post, put, delete
}}, [token]
//refresh token on regular interval
useEffect(() => {
const timerId = setInterval(() => {
// refresh token and set token
// The problems is the moment the token is refreshed, the old token might get invalidated and some calls might fail
}, tokenRefershTimeInMilliSec)
return () => {
clearInterval(timerId)
}
}, [])
return (<HttpContext.Provider {...props}>)
}
if you can are using axios, then you can check way to auto refresh or use libraries like axios-auth-refresh
Hope it points you in the right direction
I am using Remix, along with Remix-Auth and using the Twitch API/OAuth, which requires that I check in with their /validate endpoint every hour docs. I had someone recommend that I use a resource route and POST to that if the validation endpoint returned a status of 401, however, I need as I stated before the request needs to be sent every hour I figured maybe I could use something like React-Query to POST to the resource route every hour.
Just pointing out that I use createCookieSessionStorage with Remix Auth to create the session
Problem
I haven't been able to achieve the actual session being destroyed and a user being re-routed to the login page, I have left what actual code I have currently any help or suggestions to actually achieve the session being destroyed and be re-routed to the login page if the validation fails would be greatly appreciated.
// React Query client side, checks if the users token is still valid
const { error, data } = useQuery("TV-Revalidate", () =>
fetch("https://id.twitch.tv/oauth2/validate", {
headers: {
Authorization: `Bearer ${user?.token}`,
},
}).then((res) => res.json())
);
The above React Query returns this
// My attempt at the resource route
// ~routes/auth/destroy.server.ts
import { ActionFunction, redirect } from "#remix-run/node";
import { destroySession, getSession } from "~/services/session.server";
export const action: ActionFunction = async ({request}) => {
const session = await getSession(request.headers.get("cookie"))
return redirect("/login", {
headers: {
"Set-Cookie": await destroySession(session)
}
})
}
// Second attempt at resource route
// ~routes/auth/destroy.server.ts
import { ActionFunction, redirect } from "#remix-run/node";
import { destroySession, getSession } from "~/services/session.server";
export const action: ActionFunction = async ({request}) => {
const session = await getSession(request.headers.get("cookie"))
return destroySession(session)
}
I attempted using an if statement to POST to the resource route or else render the page, however, this definitely won't work as React errors out because functions aren't valid as a child and page is blank.
//index.tsx
export default function Index() {
const { user, bits, vali } = useLoaderData();
console.log("loader", vali);
const { error, data } = useQuery("TV-Revalidate", () =>
fetch("https://id.twitch.tv/oauth2/validate", {
headers: {
Authorization: `Bearer ${user?.token}`,
},
}).then((res) => res.json())
);
if (data?.status === 401)
return async () => {
await fetch("~/services/destroy.server", { method: "POST" });
};
else
return ( ... );}
You could use Remix' useFetcher hook.
https://remix.run/docs/en/v1/api/remix#usefetcher
// Resource route
// routes/api/validate
export const loader: LoaderFunction = async ({ request }) => {
const session = await getSession(request);
try {
const { data } = await fetch("https://id.twitch.tv/oauth2/validate", {
headers: {
Authorization: `Bearer ${session.get("token")}`
}
});
return json({
data
}, {
headers: {
"Set-Cookie": await commitSession(session),
}
});
} catch(error) {
return redirect("/login", {
headers: {
"Set-Cookie": await destroySession(session)
}
});
}
}
And then in your route component something like this:
const fetcher = useFetcher();
useEffect(() => {
if (fetcher.type === 'init') {
fetcher.load('/api/validate');
}
}, [fetcher]);
useEffect(() => {
if(fetcher.data?.someValue {
const timeout = setTimeout(() => fetcher.load('/api/validate'), 1 * 60 * 60 * 1000);
return () => clearTimeout(timeout);
}
},[fetcher.data]);
I am trying to logout the user when the session expires after a certain period of time. I am using redux-toolkit with react for my API calls and, hence, using the createAsyncThunk middleware for doing so.
I have around 60 API calls made in maybe 20 slices throughout my application. Also, there is a async function for logout too that is fired up on the button click. Now the problem that I am facing is that if the session expires, I am not able to logout the user automatically. If I had to give him the message, then I had to take up that message from every api call and make sure that every screen of mine has a logic to notify the Unautherised message.
I did check a method called Polling that calls an API after a certain given time. And I believe that this is not a very efficient way to handle this problem.
**Here is a little code that will help you understand how my API calls are being made in the slices of my application. **
// Here is the custom created api that has axios and withcredentials value
import axios from "axios";
const api = axios.create({
baseURL:
process.env.NODE_ENV === "development" ? process.env.REACT_APP_BASEURL : "",
headers: {
"Content-Type": "application/json",
},
withCredentials: true,
});
export default api;
// My Logout Function!!
export const logoutUser = createAsyncThunk(
"userSlice/logoutUser",
async (thunkAPI) => {
try {
const response = await api.get("/api/admin/logout");
if (response.status === 200) {
return response.data;
} else {
return thunkAPI.rejectWithValue(response.data);
}
} catch (e) {
return thunkAPI.rejectWithValue(e.response.data);
}
}
);
I want to dispatch this function whenever there is a response status-code is 401 - Unauthorised. But I don't want to keep redundant code for all my other API calls calling this function. If there is a middleware that might help handle this, that would be great, or any solution will be fine.
// Rest of the APIs are called in this way.
..........
export const getStatus = createAsyncThunk(
"orgStat/getStatus",
async (thunkAPI) => {
try {
const response = await api.get("/api/admin/orgstat");
if (response.status === 200) {
return response.data;
} else {
return thunkAPI.rejectWithValue(response.data);
}
} catch (e) {
return thunkAPI.rejectWithValue(e.response.data);
}
}
);
const OrgStatusSlice = createSlice({
name: "orgStat",
initialState,
reducers: {
.......
},
extraReducers: {
[getStatus.pending]: (state) => {
state.isFetching = true;
},
[getStatus.rejected]: (state, { payload }) => {
state.isFetching = false;
state.isError = true;
state.isMessage = payload.message;
},
[getStatus.fulfilled]: (state, { payload }) => {
state.isFetching = false;
state.data = payload.data;
},
},
});
.......
If needed any more clearence please comment I will edit the post with the same.
Thank You!!
import axios from 'axios'
import errorParser from '../services/errorParser'
import toast from 'react-hot-toast'
import {BaseQueryFn} from '#reduxjs/toolkit/query'
import {baseQueryType} from './apiService/types/types'
import store from './store'
import {handleAuth} from './common/commonSlice'
import storageService from '#services/storageService'
// let controller = new AbortController()
export const axiosBaseQuery =
(
{baseUrl}: {baseUrl: string} = {baseUrl: ''}
): BaseQueryFn<baseQueryType, unknown, unknown> =>
async ({url, method, data, csrf, params}) => {
const API = axios.create({
baseURL: baseUrl,
})
API.interceptors.response.use(
(res) => {
if (
res.data?.responseCode === 1023 ||
res.data?.responseCode === 6023
) {
if(res.data?.responseCode === 1023){
console.log('session expired')
store.dispatch(handleSession(false))
return
}
console.log('Lopgged in somewhere else')
store.dispatch(handleSession(false))
storageService.clearStorage()
// store.dispatch(baseSliceWithTags.util.resetApiState())
return
// }, 1000)
}
return res
},
(error) => {
const expectedError =
error.response?.status >= 400 &&
error.response?.status < 500
if (!expectedError) {
if (error?.message !== 'canceled') {
toast.error('An unexpected error occurrred.')
}
}
if (error.response?.status === 401) {
// Storage.clearJWTToken();
// window.location.assign('/')
}
return Promise.reject(error)
}
)
try {
let headers = {}
if (csrf) headers = {...csrf}
const result = await API({
url: url,
method,
data,
headers,
params: params ? params : '',
baseURL: baseUrl,
// signal: controller.signal,
})
return {data: result.data}
} catch (axiosError) {
const err: any = axiosError
return {
error: {
status: errorParser.parseError(err.response?.status),
data: err.response?.data,
},
}
}
}
I am also using RTK with Axios. You can refer to the attached image.
I have a fresh copy of laravel with sanctum and lighthouse. When I do the login route via axios, everything works as expected. After logging in via axios, I added a lazyquery to attempt to query some guarded fields but I get unauthenticated. I am not sure why and it has been three days I've been dealing with this. I'd really appreciate your help.
This works
useEffect(() => {
axios.defaults.withCredentials = true;
// get the token from the server
axios.get(`http://api.newods.test/sanctum/csrf-cookie`).then(function (resolve){
// try login with the user
axios.post('http://api.newods.test/api/login', {
email: 'test#test.com',
password: 'test'
}).then(function (resolve) {
console.log(`logged in ${resolve.data}`);
axios
.get("http://api.newods.test/api/gated", { withCredentials: true })
.then(function (resolve) {
console.log(`gated ${resolve.data}`);
axios
.get("http://api.newods.test/api/logout", {
withCredentials: true,
})
.then(function (resolve) {
console.log(`logged out ${resolve.data}`);
axios
.get("http://api.newods.test/api/gated", {
withCredentials: true,
})
.then(function (resolve) {
console.log(
`trying to get to gated after logging out ${resolve.data}`
);
});
});
});
});
});
}, []);
But when I cut it short and change to this, I get unauthenticated
const HELLO = gql\`
query hello {
hello
}
`;
function Home() {
const [hello, { loading, data }] = useLazyQuery(HELLO);
useEffect(() => {
axios.defaults.withCredentials = true;
// get the token from the server
axios.get(`http://api.newods.test/sanctum/csrf-cookie`).then(function (resolve){
// try login with the user
axios.post('http://api.newods.test/api/login', {
email: 'test#test.com',
password: 'test'
}).then(function (resolve) {
console.log('logged in');
});
});
}, []);
return (
<div className="container">
<div>Index</div>
<button onClick={() => hello()}>
Click to hello world
</button>
<p>{data && data.hello || ''}</p>
</div>
);
}
export default withApollo(Home);
And that returns unauthenticated when I add the #guard directive and I see the token from the axios login request is in the headers... I am not sure what I am missing here I'd greatly appreciate your help.
schema.graphql
type Query {
users: [User!]! #paginate(defaultCount: 10)
user(id: ID #eq): User #find
hello: String! #guard
me: User #auth
}
.env
SESSION_DRIVER=cookie
SESSION_LIFETIME=120
SESSION_DOMAIN=.newods.test
SANCTUM_STATEFUL_DOMAINS=newods.test:3000
config/cors.php
return [
'paths' => ['api/*', 'sanctum/csrf-cookie', 'graphql'],
'allowed_methods' => ['*'],
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => true,
];
config/lighthouse
'route' => [
/*
* The URI the endpoint responds to, e.g. mydomain.com/graphql.
*/
'uri' => '/graphql',
/*
* Lighthouse creates a named route for convenient URL generation and redirects.
*/
'name' => 'graphql',
/*
* Beware that middleware defined here runs before the GraphQL execution phase,
* make sure to return spec-compliant responses in case an error is thrown.
*/
'middleware' => [
\Nuwave\Lighthouse\Support\Http\Middleware\AcceptJson::class,
// Logs in a user if they are authenticated. In contrast to Laravel's 'auth'
// middleware, this delegates auth and permission checks to the field level.
\Nuwave\Lighthouse\Support\Http\Middleware\AttemptAuthentication::class,
],
/*
* The `prefix` and `domain` configuration options are optional.
*/
//'prefix' => '',
//'domain' => '',
],
In my next app with apollo
create.js
import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { createHttpLink } from 'apollo-link-http';
import { setContext } from 'apollo-link-context';
import Cookies from 'js-cookie';
import { serverUrl } from '../config';
export default function createApolloClient(initialState, ctx) {
// The `ctx` (NextPageContext) will only be present on the server.
// use it to extract auth headers (ctx.req) or similar.
const authLink = setContext((_, { headers }) => {
// get the authentication token from local storage if it exists
const token = Cookies.get("XSRF-TOKEN");
// console.log(`token is ${token}`);
// return the headers to the context so httpLink can read them
return {
headers: {
...headers,
"Access-Control-Allow-Credentials": true,
...(token ? { authorization: `X-XSRF-TOKEN=${token}` } : {}),
},
};
});
const httpLink = createHttpLink({
uri: serverUrl,
credentials: 'same-origin',
});
return new ApolloClient({
ssrMode: Boolean(ctx),
link: authLink.concat(httpLink),
connectToDevTools: true,
cache: new InMemoryCache().restore(initialState),
});
}
withApollo.js
import React from "react";
import Head from "next/head";
import { ApolloProvider } from "#apollo/react-hooks";
import { ApolloClient } from "apollo-client";
import { InMemoryCache } from "apollo-cache-inmemory";
import { HttpLink } from "apollo-link-http";
import fetch from "isomorphic-unfetch";
import createApolloClient from './create';
let apolloClient = null;
/**
* Creates and provides the apolloContext
* to a next.js PageTree. Use it by wrapping
* your PageComponent via HOC pattern.
* #param {Function|Class} PageComponent
* #param {Object} [config]
* #param {Boolean} [config.ssr=true]
*/
export function withApollo(PageComponent, { ssr = true } = {}) {
const WithApollo = ({ apolloClient, apolloState, ...pageProps }) => {
const client = apolloClient || initApolloClient(apolloState);
return (
<ApolloProvider client={client}>
<PageComponent {...pageProps} />
</ApolloProvider>
);
};
// Set the correct displayName in development
if (process.env.NODE_ENV !== "production") {
const displayName =
PageComponent.displayName || PageComponent.name || "Component";
if (displayName === "App") {
console.warn("This withApollo HOC only works with PageComponents.");
}
WithApollo.displayName = `withApollo(${displayName})`;
}
if (ssr || PageComponent.getInitialProps) {
WithApollo.getInitialProps = async (ctx) => {
const { AppTree } = ctx;
// Initialize ApolloClient, add it to the ctx object so
// we can use it in `PageComponent.getInitialProp`.
const apolloClient = (ctx.apolloClient = initApolloClient(
{},
ctx.req.headers.cookie
));
// Run wrapped getInitialProps methods
let pageProps = {};
if (PageComponent.getInitialProps) {
pageProps = await PageComponent.getInitialProps(ctx);
}
// Only on the server:
if (typeof window === "undefined") {
// When redirecting, the response is finished.
// No point in continuing to render
if (ctx.res && ctx.res.finished) {
return pageProps;
}
// Only if ssr is enabled
if (ssr) {
try {
// Run all GraphQL queries
const { getDataFromTree } = await import("#apollo/react-ssr");
await getDataFromTree(
<AppTree
pageProps={{
...pageProps,
apolloClient,
}}
/>
);
} catch (error) {
// Prevent Apollo Client GraphQL errors from crashing SSR.
// Handle them in components via the data.error prop:
// https://www.apollographql.com/docs/react/api/react-apollo.html#graphql-query-data-error
console.error("Error while running `getDataFromTree`", error);
}
// getDataFromTree does not call componentWillUnmount
// head side effect therefore need to be cleared manually
Head.rewind();
}
}
// Extract query data from the Apollo store
// #ts-ignore
const apolloState = apolloClient.cache.extract();
return {
...pageProps,
apolloState,
};
};
}
return WithApollo;
}
/**
* Always creates a new apollo client on the server
* Creates or reuses apollo client in the browser.
* #param {Object} initialState
*/
function initApolloClient(initialState = {}, cookie = "") {
// Make sure to create a new client for every server-side request so that data
// isn"t shared between connections (which would be bad)
if (typeof window === "undefined") {
return createApolloClient(initialState, cookie);
}
// Reuse client on the client-side
if (!apolloClient) {
// #ts-ignore
apolloClient = createApolloClient(initialState);
}
return apolloClient;
}
I have a very similar architecture, but using Vue. From comparing your code against my working implementation, I think the majority of your problems are in create.js.
I don't know much about js-cookie, but this is how I get the XSRF-TOKEN, and decode it.
let token = RegExp('XSRF-TOKEN[^;]+').exec(document.cookie)
token = decodeURIComponent(token ? token.toString().replace(/^[^=]+./, '') : '')
Then, in your setContext, you need to set the header as follows.
return {
headers: {
...headers,
'X-XSRF-TOKEN': token,
}
}
Also, I had trouble with credentials: 'same-origin' even though I'm using a subdomain. Therefore I would suggest:
const httpLink = createHttpLink({
uri: serverUrl,
credentials: 'include',
})