Hi there,
I am looking to redirect the user to the login page when a GQL API call throw an UNAUTHENTICATED error.
Note that my stack is Remix (SSR) and GQL is running on the server side.
Here is my code (redirect not working):
import https from "https";
import { onError } from "@apollo/client/link/error";
import { fromPromise } from "@apollo/client";
import { redirect, redirectDocument } from "@remix-run/node";
import { authTokenCookie } from "~/cookies.server";
async function authenticate(request: Request) {
const payload: { token: string; isLoggedIn: boolean } = await fetch(
`https://domain.tld/api/remix/auth-token`,
{
agent: new https.Agent({
rejectUnauthorized: process.env.NODE_ENV !== "development",
}),
headers: {
cookie: request.headers.get("cookie"),
},
} as RequestInit,
)
.then((response) => response.json())
.catch((error) => {
console.error(error);
return null;
});
if (!payload.isLoggedIn) {
// Redirect to login form.
throw redirectDocument(
`https://domain.tld/login?_target_path=${new URL(request.url).href}`,
);
}
// Update the token cookie for future requests.
throw redirect(request.url, {
headers: {
"Set-Cookie": await authTokenCookie.serialize(payload.token),
},
});
}
async function createClient(request: Request) {
const cookieHeader = request.headers.get("Cookie");
const authToken = cookieHeader ? await authTokenCookie.parse(cookieHeader) : null;
if (!authToken) {
throw await authenticate(request);
}
const httpLink = createHttpLink({
uri: "https://domain.tld/api/gql",
fetch, // Import from node-fetch, don't know why that is not working without this declaration...
fetchOptions: {
agent: new https.Agent({
rejectUnauthorized: process.env.NODE_ENV !== "development",
}),
},
});
const authLink = setContext((_, { headers }) => {
return {
headers: {
...headers,
cookie: cookieHeader ? await sessionCookie.parse(cookieHeader) : null,
authorization: `Bearer ${authToken}`,
},
};
});
const errorLink = onError(({ graphQLErrors, operation, forward }) => {
const isAuthenticationError = graphQLErrors?.some((error) => {
return error.extensions.code === "UNAUTHENTICATED";
});
// If it's an unauthenticated error, we refresh the token cookie (if the user is logged in) or we redirect the user to the login form.
if (isAuthenticationError) {
// THIS REDIRECT IS NOT WORKING...
return fromPromise(authenticate(request))
.filter((value) => Boolean(value))
.flatMap(() => {
return forward(operation);
});
}
// If that's another error, we log it to console.
if (graphQLErrors) {
graphQLErrors.forEach((error) => {
console.error(
`[GraphQL error]: ${error.message}`,
error,
);
});
}
});
const retryLink = new RetryLink({
delay: {
initial: 300,
max: 500,
jitter: true,
},
attempts: {
max: 3,
retryIf: (error, _operation) => !!error,
},
});
const links = [authLink, errorLink, retryLink, httpLink];
return new ApolloClient({
ssrMode: true,
cache: createApolloCache(),
link: from(links),
connectToDevTools: process.env.NODE_ENV === "development",
});
}
PS: I tried to add a redirect: "follow"
to fetchOptions
but that is not working neither.
Is it possible to make a http redirect from an Apollo link?
Thanks for your help!
David