Get token from cookie using Apollo Client and Next-Auth - next.js

can you help to check my code is there anyway that i can get access token from cookie and send it through apollo client? In my Next.js project i am using NextAuth for authentication. After user logged in I save user information and access token inside session. But i have know idea how can i get it and pass it with apollo client.
import { useMemo } from 'react'
import { ApolloClient, ApolloLink, InMemoryCache, createHttpLink } from '#apollo/client'
import { setContext } from '#apollo/client/link/context';
import { concatPagination } from '#apollo/client/utilities'
import merge from 'deepmerge'
import isEqual from 'lodash/isEqual'
export const APOLLO_STATE_PROP_NAME = '__APOLLO_STATE__'
let apolloClient
const CLIENT_URL =
process.env.NODE_ENV === 'production'
? process.env.API_END_POINT
: 'http://localhost:1337'
function createApolloClient() {
const httpLink = createHttpLink({
uri: `${CLIENT_URL}/graphql`,
credentials: 'same-origin'
});
const authLink = setContext((_, { headers }) => {
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : ''
}
}
});
const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
jobs: concatPagination(),
},
},
},
});
const client = new ApolloClient({
srMode: typeof window === 'undefined',
link: authLink.concat(httpLink),
cache
});
return client;
}
export function initializeApollo(initialState = null) {
const _apolloClient = apolloClient ?? createApolloClient()
// If your page has Next.js data fetching methods that use Apollo Client, the initial state
// gets hydrated here
if (initialState) {
// Get existing cache, loaded during client side data fetching
const existingCache = _apolloClient.extract()
// Merge the existing cache into data passed from getStaticProps/getServerSideProps
const data = merge(initialState, existingCache, {
// combine arrays using object equality (like in sets)
arrayMerge: (destinationArray, sourceArray) => [
...sourceArray,
...destinationArray.filter((d) =>
sourceArray.every((s) => !isEqual(d, s))
),
],
})
// Restore the cache with the merged data
_apolloClient.cache.restore(data)
}
// For SSG and SSR always create a new Apollo Client
if (typeof window === 'undefined') return _apolloClient
// Create the Apollo Client once in the client
if (!apolloClient) apolloClient = _apolloClient
return _apolloClient
}
export function addApolloState(client, pageProps) {
if (pageProps?.props) {
pageProps.props[APOLLO_STATE_PROP_NAME] = client.cache.extract()
}
return pageProps
}
export function useApollo(pageProps) {
const state = pageProps[APOLLO_STATE_PROP_NAME]
const store = useMemo(() => initializeApollo(state), [state])
return store
}

Related

Apollo client can't query in Nextjs getServerSideProps via SchemaLink

Apollo client fails to do query when initialized with SchemaLink (nexus) instead of HttpLink. I get this error Cannot set properties of undefined (setting 'select')
function createIsomorphLink() {
if (typeof window === 'undefined') {
const { SchemaLink } = require('#apollo/client/link/schema')
const { schema } = require('./nexusSchema')
return new SchemaLink({ schema })
} else {
const { HttpLink } = require('#apollo/client/link/http')
return new HttpLink({
uri: '/api/graphql',
credentials: 'same-origin',
})
}
}
function createApolloClient() {
return new ApolloClient({
ssrMode: typeof window === 'undefined',
link: from([errorLink, createIsomorphLink()]),
cache: new InMemoryCache(),
})
}
Here's my getServerSideProps. I initialize apollo client and use apolloClient.query(...)
export const getServerSideProps = async ({ params }) => {
const id = params.id
const apolloClient = initializeApollo()
// Here's where the error occurs
const { data } = await apolloClient.query({
query: gql`
query findUniquePage($id: String!) {
findUniquePage(where: { id: $id }) {
title
type
content
isPublished
}
}
`,
variables: { id },
})
// ...snippet..
}
I tried creating simple 'hello' query with string response, so that there was no select part. But error stayed the same.
export const getServerSideProps = async () => {
const apolloClient = initializeApollo()
const { data } = await apolloClient.query({
query: gql`query hello { hello }`,
})
return {
props: {},
}
}
My apollo client version is 3.7.0. The latest is 3.7.1 but by CHANGELOG there seems to be nothing that addresses my issue. Probably, the problem is how apollo client requests my api via SchemaLink, or how my api parses that request (apollo server + code first nexus). Because when I replaced SchemaLink with HttpLink, code worked fine. But I kinda want to keep the SchemaLink. Any advice?

How to define dataSources with #apollo/client and NextJS?

I'm trying to use NextJS with Apollo, but I can't understand where should I insert my dataSources, so I'd be able to access it through context in my resolvers (as stated in the docs).
Here's my apollo.ts (the same from NextJS with-typescript-graphql example):
import { IncomingMessage, ServerResponse } from 'http'
import { useMemo } from 'react'
import {
ApolloClient,
InMemoryCache,
NormalizedCacheObject,
} from '#apollo/client'
import resolvers from './resolvers'
import typeDefs from './schema'
let apolloClient: ApolloClient<NormalizedCacheObject> | undefined
export type ResolverContext = {
req?: IncomingMessage
res?: ServerResponse
}
function createIsomorphLink(context: ResolverContext = {}) {
if (typeof window === 'undefined') {
const { SchemaLink } = require('#apollo/client/link/schema')
const { makeExecutableSchema } = require('#graphql-tools/schema')
const schema = makeExecutableSchema({
typeDefs,
resolvers,
})
return new SchemaLink({ schema, context })
} else {
const { HttpLink } = require('#apollo/client')
return new HttpLink({
uri: '/api/graphql',
credentials: 'same-origin',
})
}
}
function createApolloClient(context?: ResolverContext) {
return new ApolloClient({
ssrMode: typeof window === 'undefined',
link: createIsomorphLink(context),
cache: new InMemoryCache(),
})
}
export function initializeApollo(
initialState: any = null,
// Pages with Next.js data fetching methods, like `getStaticProps`, can send
// a custom context which will be used by `SchemaLink` to server render pages
context?: ResolverContext
) {
const _apolloClient = apolloClient ?? createApolloClient(context)
// If your page has Next.js data fetching methods that use Apollo Client, the initial state
// get hydrated here
if (initialState) {
_apolloClient.cache.restore(initialState)
}
// For SSG and SSR always create a new Apollo Client
if (typeof window === 'undefined') return _apolloClient
// Create the Apollo Client once in the client
if (!apolloClient) apolloClient = _apolloClient
return _apolloClient
}
export function useApollo(initialState: any) {
const store = useMemo(() => initializeApollo(initialState), [initialState])
return store
}
I tried to put it alonside with typeDefs in the parameters for makeExecutable, but it's still undefined when I try to use it.
My confusion is because this setup doesn't use the new ApolloServer() constructor, as the example in the docs and I can't find some docs which relates to my setup.

Pass NextAuth JWT to Apollo Client

I am creating a nextjs app that utilized NextAuth for authentication and the generating of a JWT with custom encode and decode. The custom encode and decode is required for passing hasura claims.
How do I pass that jwt to apollo in order to append the request headers with said jwt so that hasura authenticates my requests? I looked at a few examples where people were pulling their token from ocal storage but NEXTAuth stores the JWT in a cookie. I have no idea how to access it.
I have tried adding the token to my session and reading the session with NextAuth getSession() method but it returns null.
lib\apollo.ts
import {
ApolloClient,
InMemoryCache,
ApolloLink,
HttpLink,
concat,
} from '#apollo/client'
import { WebSocketLink } from '#apollo/client/link/ws'
import { getSession } from 'next-auth/react'
// TODO - Replace URIs with environment variables
async function sesFunc() {
const sesh = await getSession()
return sesh
}
var ses = sesFunc()
console.log(`Session in apollo: ${JSON.stringify(ses)}`)
const graphLink = new HttpLink({
uri: 'http://localhost:8080/v1/graphql',
})
//GraphQL Relay Endpoint
const relayLink = new HttpLink({
uri: 'http://localhost:8080/v1beta1/relay',
})
const wsLink =
typeof window !== 'undefined'
? new WebSocketLink({
uri: 'ws://localhost:8080/v1/graphql',
options: {
reconnect: true,
},
})
: undefined
const authMiddleware = new ApolloLink((operation, forward) => {
//const { data: session, status } = useSession()
// console.log(`AuthMiddle Session: ${session}`)
operation.setContext(({ headers = {} }) => ({
headers: {
...headers,
Authorization:
'Bearer <token>',
},
}))
return forward(operation)
})
const apolloClient = new ApolloClient({
link: concat(
authMiddleware,
ApolloLink.split(
(operation) => operation.getContext().clientName === 'relayLink',
relayLink,
ApolloLink.split(
(operation) => operation.getContext().clientName === 'graphLink',
graphLink,
wsLink
)
)
),
cache: new InMemoryCache(),
})
export default apolloClient
_app.tsx
import '../styles/globals.css'
import type { AppProps } from 'next/app'
import { SessionProvider } from 'next-auth/react'
import IdleTimerContainer from '../components/IdleTimerContainer'
import Layout from '../components/Layout'
import { ApolloProvider } from '#apollo/client'
import apolloClient from '../lib/apollo'
function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) {
return (
<SessionProvider session={session}>
<ApolloProvider client={apolloClient}>
<Layout>
<Component {...pageProps} />
</Layout>
</ApolloProvider>
<IdleTimerContainer />
</SessionProvider>
)
}
export default MyApp

Get supabase `user` server side in next.js

I am attempting to get the current logged in supabase user while server side.
I have attempted to use const user = supabase.auth.user(); but I always get a null response.
I have also attempted const user = supabase.auth.getUserByCookie(req) but it also returns null. I think because I am not sending a cookie to the api when calling it from the hook.
I have tried passing the user.id from the hook to the api but the api is not receiving the parameters.
I also attempted this approach but the token is never fetched. It seems to not exist in req.cookies.
let supabase = createClient(supabaseUrl, supabaseKey);
let token = req.cookies['sb:token'];
if (!token) {
return
}
let authRequestResult = await fetch(`${supabaseUrl}/auth/v1/user`, {
headers: {
'Authorization': `Bearer ${token}`,
'APIKey': supabaseKey
}
});
`
Does anyone know how to get the current logged in user in server side code?
If you need to get the user in server-side, you need to set the Auth Cookie in the server using the given Next.js API.
// pages/api/auth.js
import { supabase } from "../path/to/supabaseClient/definition";
export default function handler(req, res) {
if (req.method === "POST") {
supabase.auth.api.setAuthCookie(req, res);
} else {
res.setHeader("Allow", ["POST"]);
res.status(405).json({
message: `Method ${req.method} not allowed`,
});
}
}
This endpoint needs to be called every time the state of the user is changed, i.e. the events SIGNED_IN and SIGNED_OUT
You can set up a useEffect in _app.js or probably in a User Context file.
// _app.js
import "../styles/globals.css";
import { supabase } from '../path/to/supabaseClient/def'
function MyApp({ Component, pageProps }) {
useEffect(() => {
const { data: authListener } = supabase.auth.onAuthStateChange((event, session) => {
handleAuthChange(event, session)
if (event === 'SIGNED_IN') {
// TODO: Actions to Perform on Sign In
}
if (event === 'SIGNED_OUT') {
// TODO: Actions to Perform on Logout
}
})
checkUser()
return () => {
authListener.unsubscribe()
}
}, [])
return <Component {...pageProps} />;
}
async function handleAuthChange(event, session) {
await fetch('/api/auth', {
method: 'POST',
headers: new Headers({ 'Content-Type': 'application/json' }),
credentials: 'same-origin',
body: JSON.stringify({ event, session }),
})
}
export default MyApp;
You can now handle this user with a state and pass it to the app or whichever way you'd like to.
You can get the user in the server-side in any Next.js Page
// pages/user_route.js
import { supabase } from '../path/to/supabaseClient/def'
export default function UserPage ({ user }) {
return (
<h1>Email: {user.email}</h1>
)
}
export async function getServerSideProps({ req }) {
const { user } = await supabase.auth.api.getUserByCookie(req)
if (!user) {
return { props: {}, redirect: { destination: '/sign-in' } }
}
return { props: { user } }
}
Here's a YouTube Tutorial from Nader Dabit - https://www.youtube.com/watch?v=oXWImFqsQF4
And his GitHub Repository - https://github.com/dabit3/supabase-nextjs-auth
supabase have a library of helpers for managing auth for both client- and server-side auth and fetching in a couple of frameworks including Next.js: https://github.com/supabase/auth-helpers and appears to be the recommended solution for similar problems based on this thread: https://github.com/supabase/supabase/issues/3783
This is how I'm using it in an API handler, but provided you have access to req, you can access the user object this way:
import { supabaseServerClient } from '#supabase/auth-helpers-nextjs';
const { user } = await supabaseServerClient({ req, res }).auth.api.getUser(req.cookies["sb-access-token"]);
Note that you will need to use the helper library supabaseClient and supabaseServerClient on the client and server side respectively for this to work as intended.
I was following a tutorial today and was having a similar issue and the below is how i managed to fix it.
I've got this package installed github.com/jshttp/cookie which is why i'm calling cookie.parse.
Supabase Instance:
`//../../../utils/supabase`
import { createClient } from "#supabase/supabase-js";
export const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_KEY
);
In my case this was my API page:
import { supabase } from "../../../utils/supabase";
import cookie from "cookie";
import initStripe from "stripe";
const handler = async (req, res) => {
const { user } = await supabase.auth.api.getUserByCookie(req);
if (!user) {
return res.status(401).send("Unathorized");
}
const token = cookie.parse(req.headers.cookie)["sb-access-token"];
supabase.auth.session = () => ({
access_token: token,
});`
const {
data: { stripe_customer },
} = await supabase
.from("profile")
.select("stripe_customer")
.eq("id", user.id)
.single();
For anyone who tries to figure out how to get the user server side with the new #supabase/auth-helpers-nextjs, Michele gave the answer.
Just a note: If you're trying to get the user on nextJs's Middleware, instead of:
... req.cookies["sb-access-token"]
You have to use: req.cookies.get('sb-access-token')
For example:
import { supabaseServerClient } from '#supabase/auth-helpers-nextjs';
const { user } = await supabaseServerClient({ req, res }).auth.api.getUser(req.cookies.get('sb-access-token'))
UPDATE: 2023. Available now on Supabase Docs here
import { createServerSupabaseClient } from '#supabase/auth-helpers-nextjs'
export default function Profile({ user }) {
return <div>Hello {user.name}</div>
}
export const getServerSideProps = async (ctx) => {
// Create authenticated Supabase Client
const supabase = createServerSupabaseClient(ctx)
// Check if we have a session
const {
data: { session },
} = await supabase.auth.getSession()
if (!session)
return {
redirect: {
destination: '/',
permanent: false,
},
}
return {
props: {
initialSession: session,
user: session.user,
},
}
}

Typescript variable being used before assigned

As per instructions followed here, I'm trying to cache my endpoint URL and token from Auth0 before constructing my Apollo client:
import React from 'react';
import { ApolloClient, ApolloProvider, from, HttpLink, InMemoryCache } from '#apollo/client';
import { setContext } from '#apollo/link-context';
import { useAuth0 } from './auth/AuthContext';
const App: React.FC = () => {
const { isLoading, getTokenSilently, getIdTokenClaims } = useAuth0();
if (isLoading) return <Loader />;
let endpoint: string;
let token: string;
const contextLink = setContext(async () => {
if (!token) {
token = await getTokenSilently();
}
if (!endpoint) {
endpoint = await getIdTokenClaims()['https://example.com/graphql_endpoint'];
}
return { endpoint, token };
});
/**
* TODO: check for autorization error and remove token from cache
* See: https://www.apollographql.com/docs/react/v3.0-beta/api/link/apollo-link-context/
*/
const apolloClient = new ApolloClient({
cache: new InMemoryCache(),
link: from([
contextLink,
new HttpLink({
uri: endpoint || '',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
}
})
])
});
return (
<ApolloProvider client={apolloClient}>
<div />
</ApolloProvider>
);
};
export default App;
I'm getting the error TS2454 (variable is used before being assigned) for both endpoint and token above. Any idea how I can get around this?
You're declaring both endpoint and token as variables, but not initializing them to anything before checking them inside of setContext.
let endpoint: string;
let token: string;
const contextLink = setContext(async () => {
if (!token) {
token = await getTokenSilently();
}
if (!endpoint) {
endpoint = await getIdTokenClaims()['https://example.com/graphql_endpoint'];
}
return { endpoint, token };
});
Try setting default values:
let endpoint: string = "";
let token: string = "";

Resources