Search code examples
javascriptgraphqlnext.jsauth0graphql-codegen

How to forward Auth0's ID token to GraphQL Code Generator?


I am using GraphQL Code Generator with React Query, this is my codegen.yml:

overwrite: true

schema: http://localhost:4000/graphql

generates:
  src/lib/__generated__/graphql.ts:
    documents:
      - "**/graphql/**/*.graphql"
      - "!mysqldata/**"
    plugins:
      - add:
          content: &comment "/* DO NOT EDIT! this file was generated by graphql-codegen */\n/* eslint-disable */"
      - add:
          placement: append
          content: "export { fetcher }"
      - typescript
      - typescript-operations
      - typescript-react-query
    config:
      fetcher:
        endpoint: "`${process.env.NEXT_PUBLIC_API_URL}/graphql`"
        fetchParams:
          credentials: include
          headers:
            Content-Type: application/json

This generates the following fetcher:

function fetcher<TData, TVariables>(query: string, variables?: TVariables) {
  return async (): Promise<TData> => {
    const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/graphql` as string, {
      method: "POST",
      credentials: "include",
      headers: {"Content-Type":"application/json"},
      body: JSON.stringify({ query, variables }),
    });
    
    const json = await res.json();

    if (json.errors) {
      const { message } = json.errors[0];

      throw new Error(message);
    }

    return json.data;
  }
}

NEXT_PUBLIC_API_URL refers to an external GraphQL API. In my Next.js application I tried to make use of nextjs-auth0 and auth0-react.

nextjs-auth0 allows me to access Auth0's ID token from Next.js API routes:

export default (req: NextApiRequest, res: NextApiResponse) => {
  const session = getSession(req, res)
  const idToken = session?.idToken

while auth0-react allows me to get the token client side:

const claims = await auth0.getIdTokenClaims();
const idToken = claims.__raw;

The problem is that because of these abstractions, I cannot figure out a way to include this token in requests to my GraphQL endpoint like:

headers: {
  authorization: `Bearer ${session?.idToken}`,
},

Solution

  • After I posted a feature request to include the ID token inside a cookie, I figured the "appSession" cookie that's set by nextjs-auth0 is an encrypted token which includes the ID token, I implemented custom server logic using the nextjs-auth0 source code as reference:

    type DecodedToken = Record<"idToken" | "token_type", string>
    
    const API_BASE_URL = "https://example.com"
    const BYTE_LENGTH = 32
    const ENCRYPTION_INFO = "JWE CEK"
    const HASH = "SHA-256"
    const alg = "dir"
    const enc = "A256GCM"
    
    /**
     * Derives appropriate sized keys from provided secret random string/passphrase using
     * HKDF (HMAC-based Extract-and-Expand Key Derivation Function) defined in RFC 8569
     * @see https://tools.ietf.org/html/rfc5869
     */
    function deriveKey(secret: string) {
      return hkdf(secret, BYTE_LENGTH, { info: ENCRYPTION_INFO, hash: HASH })
    }
    
    export const meQueryField = queryField("me", {
      type: "User",
      async resolve(_, __, ctx) {
        const jwe = ctx.request.cookies["appSession"]
    
        if (!jwe) {
          return null
        }
    
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        const octKey = JWK.asKey(deriveKey(process.env["AUTH0_SECRET"]!))
    
        const { cleartext } = JWE.decrypt(jwe, octKey, {
          complete: true,
          contentEncryptionAlgorithms: [alg],
          keyManagementAlgorithms: [enc],
        })
    
        const { idToken, token_type: tokenType } = JSON.parse(
          cleartext.toString()
        ) as DecodedToken
    
        const response = await fetch(`${API_BASE_URL}/users/me`, {
          headers: {
            Authorization: `${tokenType} ${idToken}`,
          },
        })
    
        const user = (await response.json()) as Response
    
        return {
          id: user.data.id,
          ...
        }
      },
    })
    

    It's not pretty but it works. AUTH0_SECRET is the same secret that's used to encrypt the token in nextjs-auth0