apollographql / apollographql/apollo-client-integrations

JWT Issue / Question - ApolloWrapper JWT Doesn't Exist

Open
#356 2 comments 0 reactions 0 assignees View on GitHub
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 (




menu icon




{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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.