CORS issue with client side Realm GraphQL Endpoint

Hi everyone,

First off - Kudos to the Realm team for building a great product. I am having a CORS issue when working with the GraphQL endpoint from the browser and hoping someone can lead me in the right direction. My issue is this: (apologies for the somewhat long explanation but I wanted to be as detailed as I could)

I am using a custom JWT Authentication solution, so I currently maintain all tokens for identity etc already in my app. I am able to setup a Custom JWT Authentication Authentication provider and input the corresponding JWK URI successfully in the Realm dashboard. It works fine after testing.

The problem I have is then trying to pass in the JWT in the header of the post request to Realm GraphQL endpoint https://realm.mongodb.com/api/client/v2.0/app//graphql from the browser using fetch. When I do so I get the below CORS error:

Access to fetch at ‘https://realm.mongodb.com/api/client/v2.0/app//graphql’ from origin ‘http://localhost:3000’ has been blocked by CORS policy: Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. If an opaque response serves your needs, set the request’s mode to ‘no-cors’ to fetch the resource with CORS disabled.

Please note that I am not currently using the realm-web package as I do not want to add an additional JWT token layer (realm-web maintains their own JWT token system in local storage) to my app that already has its own. I simply want to include the JWT token in the header of a fetch request to then have Realm verify against the JWK URI setup in the Realm dashboard. The docs here Authenticate GraphQL Requests make it seem like this is entirely possible as it notes that you can pass the custom JWT trough as a jwtTokenString header using a normal http request. However, I still get the CORS error when attempting this request.

I have also tried whitelisting my http://localhost:3000 in the Realm dashboard under Manage > Settings > Allowed Request Origins. Still not able to get it to work.

I was able to get several less than ideal workarounds working:

  1. Using another server as a proxy - since a node.js environment doesn’t have these CORS restrictions I was able to pass the token through to another endpoint which fetched the Realm GraphQL endpoint from a server environment successfully. (Not great as that essentially creates 2 servers)

  2. I was able to use both the realm-web package and my own system to make a more complicated auth system in which the realm-web package gave me a Bearer token to use without any CORS issues. (Not ideal as that requires 2 token systems)

Potential Solutions:
I’m not positive on why the error is happening but I believe it may have something to do with the following that someone on the Realm team would have more context on:

Are the URLS whitelisted in Allowed Request Origins mapped to the approved URLS for CORS requests?
Is the Access-Control-Allow-Headers option on the wherever the Realm server is hosted allowing a header of jwtTokenString ?

Really appreciate anyone who can help lead me in the right direction on this.

Thank you!

6 Likes

Hi @Matt_Cunningham,

Are you certain you use a PSOT method to run a qraphql query?

Using an HTTPs API to run the query should be done via a POST method as defined here:

Also you can use the Bearer method with an Access token you get from your JWT provider.

I have noticed those CORS issues when trying to use other HTTP methods like “GET”.

Best regards,
Pavel

Hi @Pavel_Duchovny thanks for the reply, yes I am using a POST request and I am confident I am running the right query because it works in a server environment. When I switch to the browser with the same query it gives CORS issues. I am using something like this, passing in the JWT as jwtTokenString shown here Custom JWT

    const result = await fetch(
      `https://myrealmgraphqlendpoint`,
      {
        method: "POST",
        headers: {
          jwtTokenString: user.token,
        },
        body: JSON.stringify({ query: FIND_MOVIES }),
      }
    );
    const json = await result.json();
    console.log(json);

Another interesting note, I found these docs Authenticate HTTP Client Requests which states

MongoDB Realm enforces non-sync rules and sync rules for all client operations. This means that requests must be made by a logged in user of your Realm app.

And those docs also offer the ability to get a Client API Access Token through the login endpoint.

But I am curious what that means - does that mean I must use the realm-web package or ping the Realm login endpoint to receive a Realm generated Bearer token?

In my case I am still wondering if I am able to just provide my custom JWT along with all of my requests to the endpoint and not have to generate another Bearer token through the login endpoint or realm-web package. Does that make sense?

1 Like

Hi @Matt_Cunningham,

I think it should work both ways otherwise it might be a bug.

You should be able to provide credentials to do both auth + query. It can be email/password but also jwt token.

Out of curiosity does a bearer token with access token doesn’t yield cors errors?

Best
Pavel

@Pavel_Duchovny Yeah I think it may be a bug. So I tried using Bearer with my JWT from my provider (not one assigned from Realm) like so:

  const result = await fetch(
      `https://myrealmgraphqlendpoint`,
      {
        method: "POST",
        headers: {
          Authorization: "Bearer " + user.token, 
        },
        body: JSON.stringify({ query: FIND_MOVIES }),
      }
    );

But now I get the below error in the console, which is probably because the Bearer strategy on the Realm endpoint is expecting a Realm assigned token format, not custom JWT format which maybe is why the jwtTokenString header exists in the first place.

{
  "error": "value of 'kid' has invalid format",
  "link": "https://realm.mongodb.com/groups/5f3ab9628951c83aa903a0b0/apps/5f5d28bcdda1ce73d48eaa42/logs?co_id=5f5f9cb6a93317dab797e984"
}

If it is a bug, I am happy to write out any further steps to reproduce. Let me know!

@Matt_Cunningham,

The bearer token can’t be your custom one but only realms.

I asked you to test it in 2 steps get a realm access token from custom-jwt/login endpoint and use it in the bearer.

Pavel

Hi @Pavel_Duchovny,

The 2 steps process works when I use the login endpoint - the login endpoint responds with an access_token and refresh_token etc. Using the provided access_token I am able to to query using the Bearer method successfully. While that process works and gives no CORS errors, it would mean I have to deal with handling refreshes of my own custom JWTs as well as the access_token that is provided back in the payload from the Realm login endpoint. Sorry if I am being repetitive here, but as you mentioned in your earlier reply

You should be able to provide credentials to do both auth + query. It can be email/password but also jwt token.

Which means there should be a scenario where I wouldn’t have to use the login endpoint at all correct?

Hi @Pavel_Duchovny,

I’m facing the same problem. I’m using an apiKey header and I’m getting the same error @Matt_Cunningham is getting.

The thing is when I try the endpoint using Postman it works but when I use Apollo client with Angular it does not!!

I hope that you have any idea about whats happening. :frowning:

Hey Hadi -

I’m assuming you’re running into this error because you’re calling it from the browser. Please consider using authorization headers https://docs.mongodb.com/realm/graphql/authenticate/#credential-headers

me too, having the same error
from browser
with apiKey or authorization, getting CORS error
i added on App Settings > Allowed request origin: localhost but not worked too

Hi

Is there an update for solution on this? Perhaps an example of how to update the apiKey header?

Many thanks!

The API key shouldn’t be used directly in the browser. Please authenticate with the API token like so.

and then use the access token to authenticate to GQL as Pavel mentioned

1 Like

I’m unable to get past CORS error (Access to fetch at … from origin … has been blocked by CORS policy: Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present…) using the browser to query the GQL endpoint. I am passing the bearer token issued by the login endpoint and the POST request works fine in Postman, including receiving the required ‘Access-Control-Allow-Origin’.

However, in the browser, the request first sends the OPTIONS request to verify CORS, and the response from the server does not contain the requisite “Access-Control-Allow-Origin”. I can duplicate this result in Postman as well by setting the request type to OPTIONS and including all of the necessary header variables (access-control-request-headers, access-control-request-method, etc.). Any thoughts on what I might be missing?

Eric, can you paste the full request that you’re sending to Realm (including all the headers, body, etc)?

We display an example of how to use Apollo Client with GraphQL here if you’re using GraphQL in the browser - realm-graphql-apollo-react/index.js at master · mongodb-university/realm-graphql-apollo-react · GitHub

:face_vomiting::face_vomiting::face_vomiting:
Unacceptable.
AWS, here we come

1 Like

G’Day, @Jason_Steele,

I acknowledge your frustration :slight_smile: Is there something we can help you with? Could you please share your use case and what you are looking for?

Could we discuss a win-win solution for you and us? :smiley:

Cheers, :performing_arts:

2 Likes

I’m having the same issue, but with Vue 3 not React and trying to use a apiKey. I’m trying to find code examples of how to get it to work but i’m not finding anything. Is there any examples for realm-graphql-apollo-vue3?

Hi guys! I am currently considering a Mongodb Atlas stack, but this issue looks like a deal breaker:

  • without the Authorization header, I don’t get CORS on either GraphQL, or HTTPS Endpoint requests
  • if I put the token in the Authorization: Bearer spot, I get the “value of ‘kid’ has invalid format” error
  • if I put the token in the jwtTokenString header, both request types work from an HTTP client, like Postman – but fail on the web, because I don’t get the CORS headers

This is a token example:

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IldvTmpxMlR5dzZJWWhlR0FhblFHTyJ9.eyJpc3MiOiJodHRwczovL2JlZXotbW9uaXRvci5ldS5hdXRoMC5jb20vIiwic3ViIjoiYXV0aDB8NjQ2Y2MyYWI2NjdkY2JjODk3ZjYyNzEyIiwiYXVkIjoiYmVlei1hcHAtbG9laHgiLCJpYXQiOjE2ODUxODY2OTUsImV4cCI6MTY4NTI3MzA5NSwiYXpwIjoiMXBuT1o3OFRybXlkSU1yMENMZGE2Q01GS1FQVkpqZ0QifQ

This is the JWKs URI: https://beez-monitor.eu.auth0.com/.well-known/jwks.json

My app is https://eu-central-1.aws.data.mongodb-api.com/app/beez-app-loehx

Is there a workaround, or am I doing something wrong?

Never mind, figured it out: as @Pavel_Duchovny mentioned, I cannot use my custom JWT from Auth0, I need to exchange it first for a Mongodb Realm token, by making a request to https://eu-central-1.aws.realm.mongodb.com/api/client/v2.0/app/app-name-here/auth/providers/custom-token/login with the payload {“token”:} – this is sort of documented here: https://www.mongodb.com/docs/atlas/app-services/users/sessions/#authenticate-a-user

You guys could save the world a lot of time spent troubleshooting this if you would:

  • consume tokens issued by any IDP
  • document the flow clearly somewhere
  • return (or log) a more relevant error than “kid has invalid format”

(either of them would do :smiley: )

Has anything changed since Pawel’s answer, or is it a special case for localhost dev environment ?

If I get the access_token from curl https://us-east-1.aws.realm.mongodb.com/api/client/v2.0/app/application-0-xxxxx/auth/providers/anon-user/login,

and then use it in graphql query:

fetch("https://us-east-1.aws.realm.mongodb.com/api/client/v2.0/app/application-0-xxxxxx/graphql", {
  "headers": {
    "accept": "*/*",
    "authorization": "Bearer eyJhbGciOiJI .... rest of the token ... _XbIM",
    "content-type": "application/json",
    "sec-ch-ua": "\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"",
    "sec-ch-ua-mobile": "?0",
    "sec-ch-ua-platform": "\"Windows\""
  },
  "referrer": "http://localhost:5173/",
  "referrerPolicy": "strict-origin-when-cross-origin",
  "body": "the long introspection query",
  "method": "POST",
  "mode": "cors",
  "credentials": "include"
});

It fails with this error:

Access to fetch at ‘https://us-east-1.aws.realm.mongodb.com/api/client/v2.0/app/application-0-xxxxx/graphql’ from origin ‘http://localhost:5173’ has been blocked by CORS policy: Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. If an opaque response serves your needs, set the request’s mode to ‘no-cors’ to fetch the resource with CORS disabled.

The preflight request:

fetch("https://us-east-1.aws.realm.mongodb.com/api/client/v2.0/app/application-0-xxxxx/graphql", {
  "headers": {
    "accept": "*/*",
    "accept-language": "en-US,en;q=0.9",
    "sec-fetch-dest": "empty",
    "sec-fetch-mode": "cors",
    "sec-fetch-site": "cross-site"
  },
  "referrer": "http://localhost:5173/",
  "referrerPolicy": "strict-origin-when-cross-origin",
  "body": null,
  "method": "OPTIONS",
  "mode": "cors",
  "credentials": "omit"
});

returned 204 with following headers:

Content-Encoding: gzip
Date: Sun, 03 Sep 2023 03:03:20 GMT
Server: mdbws
Vary: Origin,Access-Control-Request-Method,Access-Control-Request-Headers
X-Appservices-Request-Id: 64f3f7789874d3b8cb2bfa88

X-Envoy-Decorator-Operation: baas-main.baas-prod.svc.cluster.local:8086/*
X-Envoy-Upstream-Service-Time: 1
X-Frame-Options: DENY