-
Notifications
You must be signed in to change notification settings - Fork 36
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Authentification question #281
Comments
Hi Denis, authentication using Next.js is unfortunately a bit more complex :/ As you noticed, your client components will be rendered on both the server and in the client - and of course, on the server you cannot access the browser's What I would recommend is using cookies. Best would be if your Django server would be accessible from the same domain, because then you can share cookies between your Django server and your Next.js server. In that scenario, the Django server would set authentication cookies (httpOnly, secure). Then you would need to get access to those cookies during SSR. Unfortunately, the SSR run of Next.js has no direct access to cookies - you would need to pass them into your Client Components as props from a RSC component that can access cookies. To ensure that these secrets don't leak into the browser, you could use the https://github.com/phryneas/ssr-only-secrets package. |
for reference, this is how I did something similar, not sure if it is the best way: "use client";
import { ApolloLink, HttpLink } from "@apollo/client";
import { setContext } from "@apollo/client/link/context";
import {
ApolloNextAppProvider,
NextSSRInMemoryCache,
NextSSRApolloClient,
SSRMultipartLink,
} from "@apollo/experimental-nextjs-app-support/ssr";
import { readSSROnlySecret } from "ssr-only-secrets";
const getAuthLink = (cloakedSessionId: Promise<string>) =>
setContext(async (_, { headers }) => {
const sessionId = await readSSROnlySecret(
// @ts-ignore
await cloakedSessionId,
"SECRET_KEY",
);
if (sessionId) {
return {
headers: {
...(headers || {}),
Cookie: `sessionid=${sessionId}`,
},
};
}
return { headers };
});
export function ApolloWrapper({
children,
sessionId,
}: React.PropsWithChildren<{ sessionId: Promise<string> }>) {
function makeClient() {
const httpLink = new HttpLink({
uri:
typeof window === "undefined"
? "http://localhost:8000/graphql"
: "/graphql",
fetchOptions: {
cache: "no-store",
crendentials: "include",
},
});
return new NextSSRApolloClient({
cache: new NextSSRInMemoryCache(),
link:
typeof window === "undefined"
? ApolloLink.from([
new SSRMultipartLink({
stripDefer: true,
}),
getAuthLink(sessionId),
httpLink,
])
: httpLink,
});
}
return (
<ApolloNextAppProvider makeClient={makeClient}>
{children}
</ApolloNextAppProvider>
);
} import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import { ApolloWrapper } from "@/components/apollo-wrapper";
import { cookies } from "next/headers";
import { cloakSSROnlySecret } from "ssr-only-secrets";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const sessionIdPlain = cookies().get("sessionid")?.value;
const sessionId = cloakSSROnlySecret(sessionIdPlain ?? "", "SECRET_KEY");
return (
<ApolloWrapper sessionId={sessionId}>
<html lang="en">
<body className={inter.className}>{children}</body>
</html>
</ApolloWrapper>
);
} |
I am not getting authorization in headers in the apollo server context! The Error in next.js console In layout.tsx
in apollo-wrapper.tsx
|
@sayandedotcom if you log it, do you have the cookies available? Are they sent from the browser if you look into the network tab? |
yes , I logged it and it was available and it is working fine with the server component Server Component
|
As explained in the documentation for The issue I was having with To handle this issue, I updated the code as follows to make it work on both the server and the client side: On type TSignIn = { email: string; password: string }
export const signIn = async (params: TSignIn) => {
const result = await getClient().mutate<{ signIn: AuthTokenType }>({
mutation: SIGN_IN_MUTATION,
variables: {
input: {
...params
}
}
})
const accessToken = result.data?.signIn.access_token
cookies().set(authConfig.storageTokenKeyName, accessToken)
} ApolloWrapper.tsx 'use client'
import { ApolloLink, HttpLink } from '@apollo/client'
import { setContext } from '@apollo/client/link/context'
import {
ApolloNextAppProvider,
NextSSRInMemoryCache,
NextSSRApolloClient,
SSRMultipartLink
} from '@apollo/experimental-nextjs-app-support/ssr'
import { readSSROnlySecret } from 'ssr-only-secrets'
import authConfig from './authConfig'
import { getCookie } from '../utils/getCookie'
export function ApolloWrapper({ children, sessionId }: React.PropsWithChildren<{ sessionId: Promise<string> }>) {
function makeClient() {
const authLink = setContext(async (_, { headers }) => {
const cloakedSessionId = await sessionId
const accessToken =
typeof window === 'undefined'
? await readSSROnlySecret(cloakedSessionId, 'SECRET_KEY')
: getCookie(authConfig.storageTokenKeyName)
return {
headers: {
...headers,
authorization: accessToken ? `Bearer ${accessToken}` : ''
}
}
})
const httpLink = new HttpLink({
uri:
typeof window === 'undefined'
? 'https://example.com/graphql'
: `${process.env.NEXT_PUBLIC_SERVER_URL}/graphql`
})
return new NextSSRApolloClient({
cache: new NextSSRInMemoryCache(),
link:
typeof window === 'undefined'
? ApolloLink.from([
new SSRMultipartLink({
stripDefer: true
}),
authLink.concat(httpLink)
])
: authLink.concat(httpLink)
})
}
return <ApolloNextAppProvider makeClient={makeClient}>{children}</ApolloNextAppProvider>
} |
That is pretty much intentional - note that Patrick is working with cookies here. In the browser, cookies would automatically be set by the browser. There's just nothing to do in code here. But yes, if you're working with manually set headers, you'd have to do it this way. If possible, I'd recommend going cookies only though. |
Yes, in my case, I had to set headers manually because the backend is not configured with cookies. It is configured with Token authentication, so I had to use this approach. |
From error it seems like your |
I generated the secret key and still getting The only thing different from my code and docs is const httpLink = new HttpLink({
uri: typeof window === "undefined" ? "http://localhost:8000/graphql" : "http://localhost:8000/graphql",
fetchOptions: {
cache: "no-store",
crendentials: "include",
},
}); |
The key cannot be read or is incorrect, this has nothing to do with your Can you try to Did you copy-paste the example key? That one is deliberately incomplete |
I kept my own generated key in |
Everything working well except the context. The function const getAuthLink = (cloakedSessionId: Promise<string>) =>
setContext(async (_, { headers }) => {
const sessionId = await readSSROnlySecret(
// @ts-ignore
await cloakedSessionId,
"SECRET_KEY_VAR"
);
console.log("2st Console run");
if (sessionId) {
return {
headers: {
...(headers || {}),
Cookie: `sessionid=${sessionId}`,
},
};
}
return { headers };
});
export function ApolloWrapper({
children,
sessionId,
}: React.PropsWithChildren<{ sessionId: Promise<string> }>) {
function makeClient() {
const httpLink = new HttpLink({
uri: typeof window === "undefined" ? "http://localhost:8000/graphql" : "http://localhost:8000/graphql",
fetchOptions: {
cache: "no-store",
crendentials: "include",
},
});
console.log("1st Console run");
return new NextSSRApolloClient({
cache: new NextSSRInMemoryCache(),
link:
typeof window === "undefined"
? ApolloLink.from([
new SSRMultipartLink({
stripDefer: true,
}),
getAuthLink(sessionId),
httpLink,
])
: httpLink,
});
}
return <ApolloNextAppProvider makeClient={makeClient}>{children}</ApolloNextAppProvider>;
} It is also not sending any headers |
In your code snippet return new NextSSRApolloClient({
cache: new NextSSRInMemoryCache(),
link:
typeof window === "undefined"
? ApolloLink.from([
new SSRMultipartLink({
stripDefer: true,
}),
getAuthLink(sessionId),
httpLink,
])
: httpLink,
});
} you run |
Now everything seems good, I am following the same code as this #281 (comment). why the headers are not sent to the Apollo server? My apollo server app.use(
"/graphql",
cors({ credentials: true }),
expressMiddleware(server as any, {
context: async ({ req, res }) => {
console.log("req-------------", req.headers);
return {
};
},
})
); |
I am using JWT http only token. I can't set manually the token. How can I set HTTP only token on apollo client ? |
@naimulemon as long as it's the same server, that's fine and it should be sent to your Next server, where you can access it and pass it around with the methods shown in this issue. If that cookie points to a different server, you have a structural problem that makes SSR of private data impossible - but that doesn't have anything to do with this package or Next.js, more with the general concept of how cookies and the web works. At that point, you'd have to revisit you authentication architecture, or just stick to in-browser rendering and disable SSR altogether. |
Hello! I have existing app react/apollo-graphql/next
I need to ensure login/logout functionality.
My server is on python/django/graphene stack.
I have mutations to login, and refresh the JWT token.
Previously, before SSR i stored got tokens in LocalStorage and then pass into headers of every request:
and refresh token in case of expiration error:
But now I have no localStorage on my SSRed pages.
Could you please provide me with a way to manage it in a new way? I use App Router mode and my root layout is with 'use client' directive
Thanks in advance!
The text was updated successfully, but these errors were encountered: