Invalid Database Secret when making a GraphQL query using ApolloClient's `useQuery`

Stack:

I’m using Next.js and ApolloClient to execute a GraphQL query to get data that is already in my FaunaDB.

Issue

The issue I’m running into is when I try using ApolloClient’s useQuery hook:

// ...

const GET_TXNS = gql`
  query {
    getAllTxns {
      data {
        id
        description
        amount
        isCredit
        date
        imageUrl
      }
    }
  }
`

// ...

export default function Home() {
  const { txnData, error, loading } = useQuery(GET_TXNS)

  if (loading) return 'Loading'
  if (error) return `Error querying data: ${error}`

  // ...

Error:

Then, the error that gets returned to the page is:

  • Error querying data: Error: Invalid database secret.

I only have 2 keys for this db, and I know that the first worked to correctly import my schema and seed my FaunaDB.

So, that leaves the other key I have called, SERVER_KEY, but after trying either in my apollo client script, I get the same error.

Apollo Client script (./lib/client.js)

Here’s what my Apollo Client script looks like:

import { useMemo } from 'react'
import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client'

let apolloClient

function createApolloClient() {
  return new ApolloClient({
    ssrMode: typeof window === 'undefined',
    link: new HttpLink({
      uri: 'https://graphql.fauna.com/graphql',
      headers: {
        authorization: `Bearer ${process.env.SERVER_KEY}` // I tried both FAUNA_ADMIN_KEY and SERVER_KEY, and neither works
      }
    }),
    cache: new InMemoryCache()
  })
}

export function initializeApollo(initialState = null) {
  const _apolloClient = apolloClient ?? createApolloClient()

  if (initialState) {
    const existingCache = _apolloClient.extract()
    _apolloClient.cache.restore({ ...existingCache, ...initialState })
  }
  if (typeof window === 'undefined') return _apolloClient
  if (!apolloClient) apolloClient = _apolloClient
  return _apolloClient
}

export function useApollo(initialState) {
  const store = useMemo(() => initializeApollo(initialState), [ initialState ])
  return store
}

What I currently know:

I know that the request is being made because leaving the client script with no auth key for the fauna graphl endpoint, I get an error saying I’m “Invalid authorization header”

So, the request is being made; however, I have no idea what other secret to use besides the only two I made.

Would really appreciate any help!

Edits:

  • added headings to make content easier to read
  • I made a new pair of server and admin keys, and I still get the same issue.

I normally see auth headers added to apollo client like this (this is pasted from Apollo’s Docs and then edited to match your example). This is how I am using my Apollo Client. Then inside the authLink I am also checking for cookies and if I find one use that to authenticate instead of the public key I made in Fauna.

import { ApolloClient, createHttpLink, InMemoryCache } from '@apollo/client';
import { setContext } from '@apollo/client/link/context';

const httpLink = createHttpLink({
  uri: 'https://graphql.fauna.com/graphql',
});

const authLink = setContext((_, { headers }) => {
  return {
    headers: {
      ...headers,
      authorization: `Bearer ${process.env.FAUNA_ADMIN_KEY}`
    }
  }
});

const client = new ApolloClient({
  ssrMode: typeof window === 'undefined',
  link: authLink.concat(httpLink),
  cache: new InMemoryCache()
});

I have never tried adding auth headers to Apollo Client like you’re doing it in your example so I am not sure if this is going to fix your issue or not. I am also using Nextjs and I was able to get my Apollo Client running this way. One other thing you might want to check is that the key you’re using to authenticate has been given the permissions needed in Fauna’s dashboard to access the query you’re trying to run.

Hope this helps! :upside_down_face:

1 Like

Thanks for sharing your example. It definitely helps give me back some sanity since I was doing that earlier lol.

By “permissions”, are you referring to the admin and server roles for each key, or is it something else?

@yellowspaceboots I’d really appreciate another reply! Also, if you could provide a sample of your ApolloServer (graphql.js) script code?

This problem has been driving me nuts for weeks.

@platocrat Sorry I do not have an ApolloServer. I am only using Apollo Client since I am only querying Fauna. The permissions I was referring to were the Fauna “Roles” that you set in Fauna’s Security section of the dashboard. I have Public access token that I assigned a “Public” role to and then I can set that token to only allow the public users access to my login function. There is more information about that in the docs here.

But basically, all queries to Fauna require a key and the keys themselves have Roles where you can control what that Role can and can’t do. I say you “can” control access but the reality is that all of the collections, indexes and functions are opt out by default. So you have to go into the Roles and give them access to your indexes if you want them to work correctly. In my opinion, even though this causes me some headache sometimes, I think it is amazing because that means that giving my client too many permissions on accident is much much harder to do. I would rather accidentally give too little access than too much :upside_down_face:

I am not sure if any of this actually helps you with your issue. Are you having the same issue you described before? And did you try setting the graphql context like the example I gave above?

So, there is a lot to unpack here. But the tldr is that there are server/admin keys, and there are token-secrets, which are issued by Fauna’s ABAC authentication and permissions system.

If you are doing queries that have server-level permissions with a server/admin key, then I think you want to use:

headers: {
  authorization: `Basic ${server-key-here}` 
 }

However, if you are using the ABAC token-secrets, then you use:

headers: {
  authorization: `Bearer ${token-secret-here}` 
 }

(Notice the difference - Basic vs Bearer)

See this is a bit old now, but will reply in case it helps anyone else. I was having a similar difficulty trying to work this out. The solution was that the key needs encoding to base 64… to be sent with a Basic type auth header.

const b64encodedSecret = Buffer.from(`${server-key-here}:`).toString('base64')

UPDATE: It was working fine for 2 weeks and now the key has stopped working at all… hopefully an unrelated issue.