Skip to content

supabase graphql endpoint returns 401 {"message":"invalid signature"} on call with valid jwt in Authorization: Bearer header #504

Closed
@DavidOrchard

Description

@DavidOrchard

Bug report

  • I confirm this is a bug with Supabase, not with my own application.
  • I confirm I have searched the Docs, GitHub Discussions, and Discord.

Describe the bug

The graphql endpoint for my project returns 401 {"message":"invalid signature"} when I use any user's session access_token as described in the docs. I can access the graphql endpoint from the https://supabase.com/dashboard/project//api/graphiql when impersonating that user.

To Reproduce

  1. Create user in db
  2. Setup Javascript client to call graphql
  3. Call api

JS code

  const apiKey = process.env.SUPABASE_ANON_KEY
  const Authorization = options.graphqlToken
  // call the graphql endpoint
  console.log('Authorization', Authorization)
  console.log('apiKey', apiKey)
  let response
  try {
    response = await fetch(options.graphqlPath, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization,
        apiKey
      },
      body: JSON.stringify({
        query: gqlQuery,
        variables,
      }),
    })
  } catch (err) {
    console.error(`Failed to fetch from ${options.graphqlPath}. Err: `, err)
    throw new BadGateway()
  }

  if (response.status != 200 && response.status != 201) {
    // Proxied endpoint call unsuccesful, exit
    const body = await response.text()
    console.error(
      `Failed to call query endpoint! graphqlPath: ${options.graphqlPath}
Status code: ${response.status}
Token length: ${options.graphqlToken.length}
Headers: ${JSON.stringify(response.headers)}
Response body: ${body}`,
    )
    throw new BadGateway()

output is

Authorization Bearer eyJhbGciOiJIUzI1NiIsImtpZCI6Img4clRmbnZseG1xdjY1dXYiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJhdXRoZW50aWNhdGVkIiwiZXhwIjoxNzExOTg5Mzc4LCJpYXQiOjE3MTE5ODU3NzgsImlzcyI6Imh0dHBzOi8va2FiY2x5eGVzb2h1a293d3p3aWIuc3VwYWJhc2UuY28vYXV0aC92MSIsInN1YiI6IjE0MzgxMDkyLTBiMzgtNGUwMS1iNDYxLWM4MzNlYWVhMGI2YiIsImVtYWlsIjoiZXhhbXBsZUBlbWFpbC5jb20iLCJwaG9uZSI6IiIsImFwcF9tZXRhZGF0YSI6eyJwcm92aWRlciI6ImVtYWlsIiwicHJvdmlkZXJzIjpbImVtYWlsIl19LCJ1c2VyX21ldGFkYXRhIjp7fSwicm9sZSI6ImF1dGhlbnRpY2F0ZWQiLCJhYWwiOiJhYWwxIiwiYW1yIjpbeyJtZXRob2QiOiJwYXNzd29yZCIsInRpbWVzdGFtcCI6MTcxMTk4NTc3OH1dLCJzZXNzaW9uX2lkIjoiNGUxOWQ1NGYtODk2OC00ODEzLWFmOTAtYTYyMDdmMjA5YTZiIiwiaXNfYW5vbnltb3VzIjpmYWxzZX0.b5wOckaqQG020IRdLZ3QI6XOT7hHHy-I_Xjx3weor70
apiKey <hidden>
Failed to call query endpoint! graphqlPath: https://api.supabase.com/platform/projects/<project>/api/graphql
Status code: 401
Token length: 705
Headers: {}
Response body: {"message":"invalid signature"}
BadGateway [Error]

Expected behavior

I expect to have the endpoint returns the graphql.

Screenshots

If applicable, add screenshots to help explain your problem.

System information

  • OS: MacOS 14.4
  • Version of supabase-js: 2.39.3
  • Version of Node.js: 18.17.1

Additional context

Where is the "invalid signature" message generated? I looked through supabase, supabase-js, auth, auth-js, auth-helpers, pg_graphql, github.com/golang-jwt/jwt, https://pkg.go.dev/go.imperva.dev/demos/matrix-chat/internal/api/demo#ParseJWTClaims, and I could not find where that string is generated and where the 401 is returned.

The token shown above appears valid per the online jwt decoder at https://10015.io/tools/jwt-encoder-decoder as it's decoded as

{
  "aud": "authenticated",
  "exp": 1711989378,
  "iat": 1711985778,
  "iss": "https://<projectid>.supabase.co/auth/v1",
  "sub": "14381092-0b38-4e01-b461-c833eaea0b6b",
  "email": "[email protected]",
  "phone": "",
  "app_metadata": {
    "provider": "email",
    "providers": [
      "email"
    ]
  },
  "user_metadata": {},
  "role": "authenticated",
  "aal": "aal1",
  "amr": [
    {
      "method": "password",
      "timestamp": 1711985778
    }
  ],
  "session_id": "4e19d54f-8968-4813-af90-a6207f209a6b",
  "is_anonymous": false
}

JWT Header

{
  "alg": "HS256",
  "kid": "h8rTfnvlxmqv65uv",
  "typ": "JWT"
}

Separately, the docs are quite inconsistent about which headers and keys are required and the endpoints.
Relay example https://supabase.com/docs/guides/graphql/with-relay shows

fetch(`${SUPABASE_URL}/graphql/v1`
      apikey: SUPABASE_ANON_KEY,
      Authorization: `Bearer ${session?.access_token ?? SUPABASE_ANON_KEY}`,

Apollo example shows no apiKey header nor vallbck to anon key.

  const token = (await supabase.auth.getSession()).data.session?.access_token

  return {
    headers: {
      ...headers,
      Authorization: token ? `Bearer ${token}` : '',
    },

Quickstart docs at https://supabase.com/docs/guides/graphql also show /v1 in the url but my project's url clearly has no /v1 https://supabase.com/dashboard/project/<projectid>/api/graphiql. The quickstart docs link have a broken link to api, https://supabase.com/docs/guides/graphql#api-key-api_key

The graphiql explorer doesn't show the apiKey in the Headers section.

The graphql query that the explorer makes doesn't show the apiKey header.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions