treflehq / trefle-api

🍀 Trefle is a botanical JSON REST API for plants species, allowing you to search and query over all the registered species, and build the next gardening apps and farming robots.
https://trefle.io
GNU Affero General Public License v3.0
482 stars 50 forks source link

Client-side Token CORS support: no Access-Control-Allow-Origin set in the response header #23

Closed ananda-ghosh closed 4 years ago

ananda-ghosh commented 5 years ago

Describe the bug CORS support not properly implemented for client-side tokens.

To Reproduce Steps to reproduce the behavior:

  1. Try to use fetch() in a Node.js app to get a client-side token
    fetch(
    `https://trefle.io/api/auth/claim?token=${TREFLE_TOKEN}&origin=${PUBLIC_WEBROOT}`,
    {method: "POST", header: {"Content-Type": "application/json"}}
    ).then(t => console.log("client-side token", t));
  2. fetch() fails with error stating that CORS policy blocks access to the fetch data because there is no Access-Control-Allow-Origin set in the response header
    
    Access to fetch at 'https://trefle.io/api/auth/claim?token=***&origin=***' from origin '***'
    has been blocked by CORS policy: 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.

TrefleDB.js:5 POST https://trefle.io/api/auth/claimtoken=***&origin=*** net::ERR_FAILED

Uncaught (in promise) TypeError: Failed to fetch

**Expected Results**

curl -i -X POST "https://trefle.io/api/auth/claim?token=***&origin=***"

On the other hand, in cURL which does not use CORS policy because it is not a browser script, the client side token is returned.

HTTP/1.1 200 OK Server: nginx/1.14.0 (Ubuntu) Date: Sat, 17 Aug 2019 21:55:03 GMT Content-Type: application/json; charset=utf-8 Content-Length: 372 Connection: keep-alive cache-control: max-age=0, private, must-revalidate x-request-id: 2mtt8tp7c4f6ilqi4s133c53 Strict-Transport-Security: max-age=63072000; includeSubDomains; preload X-Frame-Options: SAMEORIGIN X-Content-Type-Options: nosniff

{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpcCI6WzQ4LDQ2LDQ4LDQ2LDQ4LDQ2LDQ4XSwiaXNzdWVyX2lkIjoxMTE2LCJvcmlnaW4iOiJodHRwOi8vbG9jYWxob3N0OjMwMDAiLCJhdWQiOiJKb2tlbiIsImV4cCI6MTU2NjA4NjEwMywiaWF0IjoxNTY2MDc4OTAzLCJpc3MiOiJKb2tlbiIsImp0aSI6IjJtdHQ4dHA3aGs5c2tscWk0czVyajUxMiIsIm5iZiI6MTU2NjA3ODkwM30.mmZ6QWyESWAbgTde3jJWaJrys-qnCTZ230a8tbzBtdU","expiration":1566086103}



As I understand the standard, [setting 'nosniff' for X-Content-Type-Options will always enable Cross-Origin Read Blocking on content types of 'application/json'](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options), so I think that might have to change along with setting Access-Control-Allow-Origin so that the fetch results can be read in browser code. Not that I really understand much of this, I'm pretty new to interacting with credentialed APIs. But it seems to me there are some more headers that need to set in the response on Trefle.io side for us to be able to actually use these client-side tokens in browser code.

Thanks for working on this.
lambda2 commented 5 years ago

Hey @ananda-ghosh, indeed you can't request a client-side token directly from the browser, because it will expose publicly your secret token. You must take the client side token from your server (as you do with curl), because it's safe. Then you can put the big token you received in your client side app and use this one directly in the browser.

I hope it's clear, let me know if you need more informations !

ananda-ghosh commented 5 years ago

Well I'm confused, bc the client-side token seems to be set to expire in two hours. If I want to build a public-facing website that interfaces with Trefle, would I have to run some script on my server to update the key every two hours?

At two hours, I'd need to be updating it just while I'm in development. Maybe I don't get the whole picture of how this works in a live app.

lewislbr commented 5 years ago

I have the same issue but when calling the API from the client side with a fresh JWT token using Fetch:

fetch(`https://trefle.io/api/plants?q=rosemary?token=${JWT_TOKEN}`);

Results in:

Access to fetch at 'https://trefle.io/api/plants?q=rosemary?token={JWT_TOKEN}' from origin
 'http://localhost:8080' has been blocked by CORS policy: 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.

I tried fetching passing options, even with mode: 'no-cors', but returns a 401:

fetch(`https://trefle.io/api/plants?q=${query}?token=${JWT_TOKEN}`,
      {
        method: 'GET',
        'Content-Type': 'application/json',
        'Access-Control-Allow-Origin': '*',
      }
 );
ananda-ghosh commented 5 years ago

@lewislbr Well my issue is not really the same. When I used a fresh JWT token in the rest of my browser code, I was able to get fetches to work fine. For two hours. Then I'd have to manually update my client-side token in the code. According to @lambda2, this is how it is supposed to work. So, to me, my question became, how do I build a client-side app that can call the Trefle API to search without having to update my client-side token every two hours?

I came up with an answer, and the short of it is that I used the concurrently package to run a server and a client on separate ports. My server uses a node-fetch and the regular secret token to request things from Trefle. This is not a browser call and not subject to CORS. My client calls my server through a proxy, the client-side fetch is not tagged as cross-origin either. This works great! I can go into more detail about how I accomplished this if you think you could use it, but I'll tell you that I started with this tutorial(which is linked in React documentation) to figure this all out.

lewislbr commented 5 years ago

@ananda-ghosh Thanks! I'll take a look at that tutorial and try to implement it on my application.