apollographql / apollographql/apollo-client-integrations
JWT Issue / Question - ApolloWrapper JWT Doesn't Exist
- Dominant language
- TypeScript
- Stars
- 556
- Forks
- 53
- PR merge metrics
- No merged PRs in 30d
Description
# Description
Hello all I'm relatively new to this so if this is a basic question please bear with me here. I am creating a nextjs14 app and I'm trying to implement apollo client into the application. the main part of the application is protected by a login that calls to my express backend to get a JWT token. I want to then pass the JWT token in as a header for the graphql queries.
## Issue
- When the auth is successful the JWT is generated it is available in my `apolloClient.ts` file which I will provide how I have it configured below but is not available to my `ApolloWrapper.tsx` file when trying to run a basic query after the re route to the dashboard.
- When I am re routed to the protected dashboard route and manually force a refresh the page in my browser then the JWT is present.
## Observations / Questions
- I'm guessing that when the `/dashboard` page is rendered or compiled that JWT is not accessible by the browser yet so it just sets it to undefined.
- Would it be acceptable to just force a window reload when the user gets re-routed to the `/dashboard` route?
- Am I missing something obvious that I could add to `await` the rendering of the `/dashboard` route until the JWT is available and be added to the headers in the `ApolloWrapper.tsx` file?
I have some hardcoded `urls` and such just for testing and some logs in there that would be removed once I figure this out
### Project Versions
```json
"next": "^14.2.6"
"react": "^18"
"react-dom": "^18"
"@apollo/client": "^3.11.4"
"@apollo/experimental-nextjs-app-support": "^0.11.2"
"graphql": "^16.9.0"
```
### Folder Layout
```
|-- app/
| |-- auth
| | |-- login (login form)
| | |-- page.tsx (apolloClient.ts used here but no headers needed for anon login till user is verified on backend)
| | |-- reset
| | |-- page.tsx
| | |-- layout.tsx
| |-- (root)
| |-- dashboard (folder containing page.tsx)
| |-- admin (folder containing page.tsx)
| |-- profile (folder containing page.tsx)
| |-- settings (folder containing page.tsx)
| |-- layout.tsx (ApolloWrapper.tsx used here - when doing a mutation no JWT exists for logged in user)
| |-- layout.tsx
```
### `apolloClient.ts`
```typescript
import { HttpLink, split } from "@apollo/client";
import { cookies } from "next/headers";
import {
registerApolloClient,
ApolloClient,
InMemoryCache,
} from "@apollo/experimental-nextjs-app-support";
import { createClient } from "graphql-ws";
import { GraphQLWsLink } from "@apollo/client/link/subscriptions";
import { getMainDefinition } from "@apollo/client/utilities";
import { setContext } from "@apollo/client/link/context";
export const { getClient, query, PreloadQuery } = registerApolloClient(() => {
// check if a token exists for the user
const token = cookies().get("jwt")?.value;
// Conditionally add headers
const httpLink = new HttpLink({
uri: "http://localhost:8080/v1/graphql",
fetchOptions: { cache: "no-store" },
headers: token ? { Authorization: `Bearer ${token}` } : undefined,
});
const logHeadersLink = setContext((_, { headers }) => {
const authorizationHeader = token
? { Authorization: `Bearer ${token}` }
: undefined;
const mergedHeaders = {
...headers,
...authorizationHeader,
};
console.log("Request Headers (apolloClient):", mergedHeaders);
return {
headers: mergedHeaders,
};
});
const finalHttpLink = logHeadersLink.concat(httpLink);
// Conditionally add connectionParams
const wsClient = createClient({
url: "ws://localhost:8080/v1/graphql",
connectionParams: token ? { Authorization: `Bearer ${token}` } : undefined,
on: {
connected: () => console.log("WebSocket connected"),
closed: (event) => console.log("WebSocket closed", event),
error: (error) => console.log("WebSocket error", error),
},
});
const wsLink = new GraphQLWsLink(wsClient);
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === "OperationDefinition" &&
definition.operation === "subscription"
);
},
wsLink,
// httpLink
finalHttpLink
);
return new ApolloClient({
cache: new InMemoryCache({ addTypename: false }),
link: splitLink,
});
});
```
### `ApolloWrapper.tsx`
```typescript
"use client";
import { loadErrorMessages, loadDevMessages } from "@apollo/client/dev";
import { GraphQLWsLink } from "@apollo/client/link/subscriptions";
import { getMainDefinition } from "@apollo/client/utilities";
import { split, HttpLink } from "@apollo/client";
import { setVerbosity } from "ts-invariant";
import { createClient } from "graphql-ws";
import {
ApolloClient,
InMemoryCache,
ApolloNextAppProvider,
} from "@apollo/experimental-nextjs-app-support";
import { setContext } from "@apollo/client/link/context";
// set debugging mode to true when in development mode
if (process.env.NODE_ENV === "development") {
setVerbosity("debug");
loadDevMessages();
loadErrorMessages();
}
const makeClient = (token: string | undefined) => {
const httpLink = new HttpLink({
uri: "http://localhost:8080/v1/graphql",
fetchOptions: { cache: "no-store" },
headers: {
Authorization: token ? `Bearer ${token}` : "",
},
});
const logHeadersLink = setContext((_, { headers }) => {
const authorizationHeader = token
? { Authorization: `Bearer ${token}` }
: undefined;
const mergedHeaders = {
...headers,
...authorizationHeader,
};
console.log("mergedHeaders :", mergedHeaders);
return {
headers: mergedHeaders,
};
});
const finalHttpLink = logHeadersLink.concat(httpLink);
const wsClient = createClient({
url: "ws://localhost:8080/v1/graphql",
connectionParams: {
Authorization: token ? `Bearer ${token}` : "",
},
on: {
connected: () => console.log("WebSocket connected"),
closed: (event) => console.log("WebSocket closed", event),
error: (error) => console.log("WebSocket error", error),
},
});
const wsLink = new GraphQLWsLink(wsClient);
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === "OperationDefinition" &&
definition.operation === "subscription"
);
},
wsLink,
// httpLink
finalHttpLink
);
return new ApolloClient({
ssrMode: true,
cache: new InMemoryCache(),
link: splitLink,
});
};
// you need to create a component to wrap your app in
export function ApolloWrapper({
children,
jwt,
}: React.PropsWithChildren<{ jwt?: string }>) {
return (
makeClient(jwt)}>
{children}
);
}
```
### `Layout.tsx` : app/(root)/layout.tsx
```typescript
import { getUser } from "@/lib/actions/user.actions";
import MobileNav from "@/components/MobileNav";
import SideNav from "@/components/SideNav";
import Image from "next/image";
import { ApolloWrapper } from "@/components/ApolloWrapper";
import { cookies } from "next/headers";
export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const user = await getUser();
if (user) {
const jwt = cookies().get("jwt")?.value;
return (
{children}
);
}
}
```
Contributor guide
No contributing guide indexed for this repository
Research direction
Read app/(root)/layout.tsx, ApolloWrapper.tsx, and apolloClient.ts first, then trace the login-to-dashboard navigation and JWT cookie availability. Reproduce the first dashboard query and compare the token and headers in both client configurations. Done means identifying the timing or data-flow cause and documenting whether a refresh is required, with a clear recommendation for the reported setup.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- express, graphql, next.js, react, typescript
- Domain
- api, authentication, frontend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100