-
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
examples handling authenticated clients #21
Comments
@masterkain that's a good suggestion! I think I can update the polls demo to add a section that needs authentication :) Do you have any preference regarding auth system? I'll try with a cookie based session if not 😊 |
cookies / auth token would be a blessing for me, need to better understand what to do when a client becomes unauthenticated, how to properly pass auth data to the client after login, etc. ❤️ |
This library is amazing for firebase https://github.com/awinogrodzki/next-firebase-auth-edge easy examples |
I just have it doing this now layout.tsx import { getTokens } from 'next-firebase-auth-edge/lib/next/tokens';
import { cookies } from 'next/dist/client/components/headers';
import { ApolloWrapper } from '../components/apollo-wrapper';
import { AuthProvider } from '../components/auth-provider';
import { mapTokensToTenant } from '../lib/firebase/auth';
import { serverConfig } from '../lib/firebase/server-config';
import './global.css';
export const metadata = {
title: 'Nx Next App',
description: 'Generated by create-nx-workspace',
};
export default async function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
const tokens = await getTokens(cookies(), {
serviceAccount: serverConfig.serviceAccount,
apiKey: serverConfig.firebaseApiKey,
cookieName: 'AuthToken',
cookieSignatureKeys: ['secret1', 'secret2'],
});
const tenant = tokens ? mapTokensToTenant(tokens) : null;
return (
<html lang="en">
<body>
<AuthProvider defaultTenant={tenant}>
<ApolloWrapper token={tokens?.token}>{children}</ApolloWrapper>
</AuthProvider>
</body>
</html>
);
} apollo-wrapper.tsx 'use client';
import {
ApolloClient,
ApolloLink,
HttpLink,
SuspenseCache,
} from '@apollo/client';
import {
ApolloNextAppProvider,
NextSSRInMemoryCache,
SSRMultipartLink,
} from '@apollo/experimental-nextjs-app-support/ssr';
import React from 'react';
const uri = process.env.NEXT_PUBLIC_HASURA_URL;
function createClient(token: string | undefined) {
const httpLink = new HttpLink({
uri,
headers: token
? {
Authorization: `Bearer ${token}`,
}
: {
'x-hasura-admin-secret': 'myadminsecretkey',
},
});
return new ApolloClient({
cache: new NextSSRInMemoryCache(),
link:
typeof window === 'undefined'
? ApolloLink.from([
new SSRMultipartLink({
stripDefer: true,
}),
httpLink,
])
: httpLink,
});
}
function makeSuspenseCache() {
return new SuspenseCache();
}
export function ApolloWrapper({
children,
token,
}: React.PropsWithChildren<{
token: string | undefined;
}>) {
const makeClient = React.useCallback(() => createClient(token), [token]);
return (
<ApolloNextAppProvider
makeClient={makeClient}
makeSuspenseCache={makeSuspenseCache}
>
{children}
</ApolloNextAppProvider>
);
} |
Yup, that's a very good approach! Just to highlight the important parts from your code snippet to make it easier for other people following along:
|
Thanks @seanaguinaga! How do you update the given token when it expired ? |
Any examples of doing this with Next-Auth? Also, the example above is for client-side auth. Is there a way to use authentication with RSC? Do we need to manually pass the context into every call? We used to pass the context into the createIsomorphicLink function like so: type ResolverContext = {
req?: IncomingMessage
res?: ServerResponse
}
function createIsomorphicLink(context?: ResolverContext) {
if (typeof window === 'undefined') {
const { SchemaLink } = require('@apollo/client/link/schema')
const schema = makeExecutableSchema({
typeDefs: typeDefs,
})
return new SchemaLink({ schema, context })
} else {
const { HttpLink } = require('@apollo/client')
return new HttpLink({
uri: '/api/graphql',
credentials: 'same-origin',
})
}
} Is there a way we can do this in the getClient function to have some context on the Server Side calls? Can we use SchemaLink with getClient? |
@rafaelsmgomes You probably don't need context here, in Next.js you should be able to just call
I don't see a reason why not, but be aware that if you have any global variables like |
The auth library does that for me, thankfully |
Hi, @phryneas! I still don't understand how to crack this one. This is how I used to authenticate the getServerSideProps function: export async function getServerSideProps(ctx: GetServerSidePropsContext<{ symbol: string }>) {
const { symbol } = ctx.params!
const { req, res } = ctx
const apolloClient = initializeApollo({})
const context: DefaultContext = {
headers: {
cookie: req.headers?.cookie, // this is where I'm passing the cookies down to authenticate
},
}
await apolloClient.query({
query: GET_PROFILE_CHART,
variables: { symbol, fromDate },
context,
}),
return addApolloState(apolloClient, {
props: { symbol },
})
} I'm trying to authenticate my user on the register client function calls via: import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client'
import { registerApolloClient } from '@apollo/experimental-nextjs-app-support/rsc'
import { cookies } from 'next/dist/client/components/headers'
export const { getClient } = registerApolloClient(() => {
let nextCookies = cookies()
.getAll()
.reduce((acc, cur) => {
const { name, value } = cur
acc += `${name}:${value}`
return acc
}, '')
return new ApolloClient({
cache: new InMemoryCache(),
link: new HttpLink({
// https://studio.apollographql.com/public/spacex-l4uc6p/
// uri: '/api/graphql',
uri: 'http://localhost:3000/api/graphql',
headers: {
cookie: nextCookies,
},
credentials: 'same-origin',
// you can disable result caching here if you want to
// (this does not work if you are rendering your page with `export const dynamic = "force-static"`)
// fetchOptions: { cache: "no-store" },
}),
})
}) That did not work, but neither did passing the context in the getClient function: let nextCookies = cookies()
.getAll()
.reduce((acc, cur) => {
const { name, value } = cur
acc += `${name}:${value}`
return acc
}, '')
const { data } = await getClient().query({
query: TEST_QUERY,
variables: { symbol },
context: { headers: { cookie: nextCookies } },
}) I thought this would work, and I don't see another way of doing it. Maybe I added the cookies in a wrongful way? But that would mean the headers need to be passed differently now? Maybe I have to call cookies in a different way. Or not pass it in the same manner as it works in the frontend. I don't know. |
@rafaelsmgomes It feels to me that both of these should be working - have you tried to |
Hi @phryneas! Thanks for letting me know as I pursuing the right solution! The issue was within the keyboard and the chair on my side of the screen. I'll put it here in case anyone is wondering: let nextCookies = cookies()
.getAll()
.reduce((acc, cur, i, arr) => {
const { name, value } = cur
acc += `${name}=${value}${arr.length - 1 !== i ? '; ' : ''}` // forgot to give it a space after each cookie. Also, was using ":" instead of "="
return acc
}, '') |
Is there an example of how to use this with Next Auth? I'm having a lot of issues with getting this to work in both client and server side components. |
One issue I'm having is that, my login page is part of the same app. my flow is
The dashboard information is still showing information from Credentials A. The components are mix of "use client" and "use server". When console logging the token it only ever gets set on when as a work around for now I do any ideas what i might be doing wrong or solution for this issue? |
Looking for some help on this myself, I am just trying to send in a new token/create a new authorization header when a user logins. My setToken function gets called when a successful login comes back, I noticed my makeClient was never being called again...Any help would be greatly appreciated! :D export const Wrapper = ({children}: {children: React.ReactNode}) => {
const [token, setToken] = React.useState<string>()
const makeClient = React.useCallback(() => createClient(token), [token])
return (
<ApolloNextAppProvider makeClient={makeClient}>
<ToastProvider>
<UserProvider setToken={setToken}>{children}</UserProvider>
</ToastProvider>
</ApolloNextAppProvider>
)
} |
Seems like currently the only way is to save token in local storage/cookies and reload page. |
@ben-hapip @chvllad You should never recreate your whole ApolloClient instance when an authentication token changes. The best way to solve this would storing the auth token in a way that is transparent to Apollo Client (at least in the browser) - in a httpOnly secure cookie. |
Ayyy thanks fellas for the suggestions!! 🤝 |
Has this been solved @ben-hapip ? I tried your solution @phryneas but it does not work for me :( |
@romain-hasseveldt As I said before, If you show a code example here, I can show you how to leverage a |
Hello @phryneas , thank you for the promptness of your response. Here is what my current implementation looks like: 'use client';
import { ApolloLink, from } from '@apollo/client';
import { setContext } from '@apollo/client/link/context';
import {
ApolloNextAppProvider,
NextSSRInMemoryCache,
NextSSRApolloClient,
SSRMultipartLink,
} from '@apollo/experimental-nextjs-app-support/ssr';
import { createUploadLink } from 'apollo-upload-client';
import { User } from 'next-auth';
import { useSession } from 'next-auth/react';
function makeClient(user?: User) {
const httpLink = createUploadLink({
uri: process.env.NEXT_PUBLIC_BACK_GRAPHQL_URL,
}) as unknown as ApolloLink;
const authLink = setContext((_, { headers }) => {
return {
headers: {
...headers,
authorization: user ? `Bearer ${user.jwt}` : '',
},
};
});
const links = [authLink, httpLink];
return new NextSSRApolloClient({
cache: new NextSSRInMemoryCache(),
link:
typeof window === 'undefined'
? from(
[
new SSRMultipartLink({
stripDefer: true,
}),
links,
].flat(),
)
: from(links),
});
}
export function ApolloWrapper({ children }: React.PropsWithChildren) {
const { data: session } = useSession();
return (
<ApolloNextAppProvider makeClient={() => makeClient(session?.user)}>
{children}
</ApolloNextAppProvider>
);
} |
In that case, you need to move export function ApolloWrapper({ children }: React.PropsWithChildren) {
const { data: session } = useSession();
const sessionRef = useRef(session);
useEffect(() => {
sessionRef.current = session;
}, [session])
function makeClient() {
const httpLink = createUploadLink({
uri: process.env.NEXT_PUBLIC_BACK_GRAPHQL_URL,
}) as unknown as ApolloLink;
const authLink = setContext((_, { headers }) => {
return {
headers: {
...headers,
authorization: sessionRef.current.user ? `Bearer ${sessionRef.current.user.jwt}` : "",
},
};
});
const links = [authLink, httpLink];
return new NextSSRApolloClient({
cache: new NextSSRInMemoryCache(),
link:
typeof window === "undefined"
? from(
[
new SSRMultipartLink({
stripDefer: true,
}),
links,
].flat()
)
: from(links),
});
}
return (
<ApolloNextAppProvider makeClient={makeClient}>
{children}
</ApolloNextAppProvider>
);
} |
It does not seem to work (at least in my case). The value of |
And you're actually accessing One correction though: - authorization: user ? `Bearer ${sessionRef.current.user.jwt}` : "",
+ authorization: sessionRef.current.user ? `Bearer ${sessionRef.current.user.jwt}` : "", |
This is the current state of my implementation: export function ApolloWrapper({ children }: React.PropsWithChildren) {
const { data: session } = useSession();
const sessionRef = useRef(session);
useEffect(() => {
sessionRef.current = session;
}, [session]);
function makeClient() {
const httpLink = createUploadLink({
uri: process.env.NEXT_PUBLIC_BACK_GRAPHQL_URL,
}) as unknown as ApolloLink;
const authLink = setContext((_, { headers }) => {
return {
headers: {
...headers,
authorization: sessionRef.current?.user
? `Bearer ${sessionRef.current?.user.jwt}`
: '',
},
};
});
const links = [authLink, httpLink];
return new NextSSRApolloClient({
cache: new NextSSRInMemoryCache(),
link:
typeof window === 'undefined'
? from(
[
new SSRMultipartLink({
stripDefer: true,
}),
links,
].flat(),
)
: from(links),
});
}
return (
<ApolloNextAppProvider makeClient={makeClient}>
{children}
</ApolloNextAppProvider>
);
} |
And if you add some
and here
what log(and which order) do you get? |
I have the following:
|
Old code suggestionconst sessionRef = useRef(session);
// assign on every render to keep it up-to-date with the latest value
sessionRef.current = session; Edit: Apparently React deems this as unsafe, which is something I did not know about until now. Please ignore my suggestion 🙂 This is also mentioned in the |
Thank you for trying @jerelmiller :) I tested out of curiosity, and... it doesn't work either. |
This is honestly weird - could you try to create a small reproduction of that? |
So I believe I got token auth working for
"use client";
// ^ this file needs the "use client" pragma
// https://github.com/apollographql/apollo-client-nextjs
import { ApolloLink, HttpLink } from "@apollo/client";
import {
ApolloNextAppProvider,
NextSSRInMemoryCache,
NextSSRApolloClient,
SSRMultipartLink,
} from "@apollo/experimental-nextjs-app-support/ssr";
import { envvars } from "@lib/envvars";
// have a function to create a client for you
function makeClient(token:string|undefined) {
const httpLink = new HttpLink({
// this needs to be an absolute url, as relative urls cannot be used in SSR
uri: envvars.API_URI,
headers: {
Authorization: token ? `Bearer ${token}` : '',
},
// you can disable result caching here if you want to
// (this does not work if you are rendering your page with `export const dynamic = "force-static"`)
fetchOptions: { cache: "no-store" },
// you can override the default `fetchOptions` on a per query basis
// via the `context` property on the options passed as a second argument
// to an Apollo Client data fetching hook, e.g.:
// const { data } = useSuspenseQuery(MY_QUERY, { context: { fetchOptions: { cache: "force-cache" }}});
});
return new NextSSRApolloClient({
// use the `NextSSRInMemoryCache`, not the normal `InMemoryCache`
cache: new NextSSRInMemoryCache(),
link:
typeof window === "undefined"
? ApolloLink.from([
// in a SSR environment, if you use multipart features like
// @defer, you need to decide how to handle these.
// This strips all interfaces with a `@defer` directive from your queries.
new SSRMultipartLink({
stripDefer: true,
}),
httpLink,
])
: httpLink,
});
}
// you need to create a component to wrap your app in
export function ApolloWrapper({ token, children }: React.PropsWithChildren<{
token: string | undefined;
}>) {
return (
<ApolloNextAppProvider makeClient={() => makeClient(token)}>
{children}
</ApolloNextAppProvider>
);
} I'm also trying to figure out auth via Server side
import { HttpLink } from "@apollo/client";
import {
NextSSRInMemoryCache,
NextSSRApolloClient,
} from "@apollo/experimental-nextjs-app-support/ssr";
import { registerApolloClient } from "@apollo/experimental-nextjs-app-support/rsc";
import { envs } from "@/envs";
import { cookies } from "next/headers";
export const { getClient } = registerApolloClient(() => {
const cookieStore = cookies()
const cookieSession = cookieStore.get('keystonejs-session')
console.log('cookieSession::::: ');
const token = cookieSession?.value
console.log(`==== Bearer ${token}`);
return new NextSSRApolloClient({
cache: new NextSSRInMemoryCache(),
link: new HttpLink({
uri: envs.API_URI,
}),
headers: {
'Authorization': (token) ? `Bearer ${token}`: "",
'Content-Type': 'application/json'
},
})
}) I know I'm getting the right session token because It works when I manually set the token in Apollo Studio Sandbox it works, but If I manually set the token inside this a linke to the full source code git repo |
Hi everyone, I'm encountering an issue with Apollo Client and Auth.js in my Next.js application. The problem is that when making GraphQL requests, the cookies are being overwritten by Auth.js, causing my API to respond that the necessary authorization cookie was not sent. Current setup:
The issue: I've tried several approaches, however, these attempts haven't resolved the cookie conflict with Auth.js on the client side. Questions:
Any insights or suggestions would be greatly appreciated. If you need any additional information about my setup, please let me know. Here's my Apollo Client configuration: 'use client';
import { type PropsWithChildren } from 'react';
import { ApolloLink, HttpLink } from '@apollo/client';
import { loadDevMessages, loadErrorMessages } from '@apollo/client/dev';
import { setContext } from '@apollo/client/link/context';
import {
ApolloClient,
ApolloNextAppProvider,
InMemoryCache,
SSRMultipartLink,
} from '@apollo/experimental-nextjs-app-support';
import { useSession } from 'next-auth/react';
if (process.env.NODE_ENV === 'development') {
loadDevMessages();
loadErrorMessages();
}
export function ApolloWrapper({ children }: PropsWithChildren) {
const { data: session } = useSession();
const makeClient = () => {
// Create an HTTP link to the GraphQL API
const httpLink = new HttpLink({
uri: process.env.NEXT_PUBLIC_API_URL,
fetchOptions: { cache: 'no-store' },
credentials: 'include',
});
// Create an authentication link to set the appropriate headers
const authLink = setContext((_, { headers }) => {
return {
headers: {
...headers,
cookie: headers?.cookie.concat(
session
? `access-token=${session?.access_token}; refresh-token=${session?.access_token}`
: '',
),
},
};
});
// Create the links for the client and server
const clientLinks = [authLink, httpLink];
const ssrLinks = [
new SSRMultipartLink({ stripDefer: true }),
...clientLinks,
];
// Create the Apollo Client
return new ApolloClient({
cache: new InMemoryCache(),
link: ApolloLink.from(
typeof window === 'undefined' ? ssrLinks : clientLinks,
),
});
};
if (!session) return null;
return (
<ApolloNextAppProvider makeClient={makeClient}>
{children}
</ApolloNextAppProvider>
);
} Thank you in advance for your help! |
this has been a torn in my side for a while, if you can provide an example with authenticated clients that would be super.
The text was updated successfully, but these errors were encountered: