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}`,
},
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