#StackBounty: #javascript #asp.net-core #cors #jwt How to prevent desktop apps from mimicking browser requests?

Bounty: 50

I have two Web Apps written in .netcore, App 1 served the html content and App 2 is the API that serves the requests of Javascript, both are in the same solution but each have a different port ofcourse. My Scenario is the browser returns the website from App 1 that includes the signup form and the javascript access the signup functions on the API at App 2.

To prevent the API from being accessed by any other website I enabled the CORS and added the domain of App 1 as the only domain allowed to access the API of App 2 and it worked great but any desktop can mimick this same request headers and access the API and I tested with postman and the API was accessed.

So I added Authorization header so that all the API functions are required to authorize the JWT bearer token to be accessed.

The problem is how to prevent desktop apps or other non-browser apps in general from accessing it.because of the following:

1- if I put the access token in the response from App 1 so that javascript can use it to access App 2 , any other app can get it easily and copy paste it to their app and API will be accessed.

2- If I don’t hardcode the token in the response of App 1 and instead let the javascript access a route that generate a token, then any app can do the same because they can mimick the same request headers of the browser. and the CORS will be useless then.

so what should I do ?


Get this bounty!!!

#StackBounty: #javascript #security #promise #cors #indexeddb Nested cross-origin iframes for secure user-configurable javascript tools

Bounty: 50

Context

I’m trying to build a system in which a tool (the Client) will generate a header to be used as part of an HTTP request from the user’s browser.

  • The user should be able to choose their own implementation of the Client.
  • The user should not have to install a plugin or extension to their browser.

Summary

A 3rd party will serve a small wrapper (the Shim) which will keep track of where to load the Client from. It will store this in the browser’s IndexedDB under its own origin.

The Shim and the Client will be loaded in iframes of their own origin, so that they (and the Host website) can only access each-other’s functionality through the defined methods (based on MessageChannels and postMessage() calls).

Parties, Prerogatives, and Restrictions

  • The Host website is at www.host.com.
    • They shouldn’t be able to know what implementation of the Client is in use,
    • nor should they be able to affect the Client in any way except by requesting header values.
    • (As it stands the Host is able to suggest a default Client; when I actually build the set-Client-address tool for the Shim, I’ll get rid of this.)
  • The Shim is served from www.shim.com.
    • They’re assumed to be trustworthy in the sense that people know what code they’re serving. (Even this isn’t ideal; subresource integrity checks would be great if they worked on iframes.)
    • Their code obviously knows who the Client is, and can see the requests and responses as they’re passed back and forth, but all of this stays on the user’s machine as far as the Shim is concerned.
    • They can only affect the client by requesting the header values.
  • The Client is served from www.client.com.
    • At present this is being passed in as a default, but in practice it should have been saved into the IndexedDB on the user’s machine belonging to the www.shim.com origin. (It will get put there by some other tool I haven’t written yet.)
    • It’s fine for the client to know who the Host is; I may even add that as an explicit property of each request.
  • There’s an image or some other resource that the Host wants to load on their page, but requesting that image requires a custom Receitps-Receipt HTTPS header, the value for which needs to come from the Client. Let’s suppose this image is at https://www.target.com/target.png, but it could just as easily be in the same origin as the Host website itself.

Code

www.host.com/host.html

<!doctype html>
<html lang="en-US">
  <head>
    <meta charset="utf-8">
    <title>Host</title>
    https://www.shim.com/shim.js
    
        _page_loaded = ()=>{
            window.FOTR.fetch(
                new Request('https://www.target.com/target.png')
                ).then((response)=>{
                    return response.blob();
                }).then((b)=>{
                    const i = window.document.createElement('img');
                    i.src = URL.createObjectURL(b);
                    document.getElementById("testTarget").appendChild(i);
                });
        };
    
  </head>
  <body onload="_page_loaded()">
    <h1>Host</h1>
    <p>Lorem ipsum dolor sit amet.</p>
    <p id="testTarget"></p>
    <p>Text continues.</p>
  </body>
</html>

www.shim.com/shim.js

((toolName)=>{
    if(typeof window[toolName] === 'undefined'){

        const loaderUtilities = { //This is exactly the same between shim.html and shim.js.

            domReady: new Promise((resolve, reject)=>{
                if(document.readyState === "loading"){
                    document.addEventListener('DOMContentLoaded', resolve);
                }
                else{
                    resolve();
                }   // add error handler?
            }),

            origin: (uri)=>{
                const parser = window.document.createElement('a');
                parser.href = uri;
                return `${parser.protocol}//${parser.host}`;
            },

            loadTool: (uri)=>{
                return new Promise((resolve, reject)=>{
                    const tag = window.document.createElement('iframe');
                    tag.src = uri;
                    tag.width = 0;
                    tag.height = 0;
                    tag.style = "visibility: hidden";
                    window.addEventListener("message",
                        (e)=>{
                            if(e.origin == loaderUtilities.origin(uri)){ //is it possible to refine the origin check?
                                resolve(e.data);
                            }
                        }, 
                        false);
                    loaderUtilities.domReady.then(()=>{
                        document.body.appendChild(tag);
                    });
                });   // add error handler?
            },

            requestOverPort: (port, resource)=>{
                return new Promise((resolve, reject)=>{
                    const disposableChannel = new MessageChannel();
                    disposableChannel.port1.onmessage = (e)=>{
                        resolve(e.data);
                        disposableChannel.port1.close();
                    };
                    port.postMessage(
                        {
                            resource: resource,
                            port: disposableChannel.port2
                        },
                        [disposableChannel.port2]);
                });
            },
        };

        const defaultClient = document.currentScript.getAttribute("data-default") || '';
        const gotClientPort = loaderUtilities.loadTool(`https://www.shim.com/shim.html#${defaultClient}`);

        window[toolName] = {
            fetch: (request)=>{
                return gotClientPort
                    .then((clientPort)=>{
                        return loaderUtilities.requestOverPort(
                            clientPort,
                            {
                                url: request.url,
                                method: request.method
                            });
                    })
                    .then((receipt)=>{
                        return window.fetch(
                            request,
                            {
                                headers: new Headers({ 'Receipts-Receipt': receipt }),
                            });
                    });
            }
        }

    }
})(document.currentScript.getAttribute("data-name") || "FOTR")

www.shim.com/shim.html

<!DOCTYPE html>
<html>
  <head>
    <title>The FOTR Shim</title>
    <meta charset="UTF-8">
    

        const loaderUtilities = { //This is exactly the same between shim.html and shim.js.

            domReady: new Promise((resolve, reject)=>{
                if(document.readyState === "loading"){
                    document.addEventListener('DOMContentLoaded', resolve);
                }
                else{
                    resolve();
                }   // add error handler?
            }),

            origin: (uri)=>{
                const parser = window.document.createElement('a');
                parser.href = uri;
                return `${parser.protocol}//${parser.host}`;
            },

            loadTool: (uri)=>{
                return new Promise((resolve, reject)=>{
                    const tag = window.document.createElement('iframe');
                    tag.src = uri;
                    tag.width = 0;
                    tag.height = 0;
                    tag.style = "visibility: hidden";
                    window.addEventListener("message",
                        (e)=>{
                            if(e.origin == loaderUtilities.origin(uri)){ //is it possible to refine the origin check?
                                resolve(e.data);
                            }
                        }, 
                        false);
                    loaderUtilities.domReady.then(()=>{
                        document.body.appendChild(tag);
                    });
                });   // add error handler?
            },

            requestOverPort: (port, resource)=>{
                return new Promise((resolve, reject)=>{
                    const disposableChannel = new MessageChannel();
                    disposableChannel.port1.onmessage = (e)=>{
                        resolve(e.data);
                        disposableChannel.port1.close();
                    };
                    port.postMessage(
                        {
                            resource: resource,
                            port: disposableChannel.port2
                        },
                        [disposableChannel.port2]);
                });
            },
        };

        const indexedDBPromise = (request)=>{
            return new Promise(
                (resolve, reject)=>{
                    request.onsuccess = (e)=>{
                        resolve(e.target.result);
                    };
                    request.onerror = (e)=>{
                        reject(e.target.error);
                    };
                });
        };

        const defaultClient = window.location.href.split('#')[1] || '';
        const objectStoreName = "chosen_clients";

        const openDB = window.indexedDB.open("FOTR", 1);
        openDB.onupgradeneeded = (e)=>{
            e.target.result.createObjectStore(objectStoreName);
        };

        indexedDBPromise(openDB)
            .then(
                (db)=>{
                    const tx = db.transaction(objectStoreName, "readonly");
                    tx.oncomplete = ()=>{
                        db.close();
                    };
                    return indexedDBPromise(tx.objectStore(objectStoreName).getAll());
                })//handle open-db error?
            .then(
                (db_result)=>{
                    const clientURI = db_result.uri || defaultClient;
                    return loaderUtilities.loadTool(clientURI);
                })//handle db-read error?
            .then(
                (innerPort)=>{
                    const outerChannel = new MessageChannel();
                    outerChannel.port1.onmessage = (e)=>{
                        const newPort = e.data.port;
                        const request = e.data.resource;
                        console.log(`Forwarding request for [${request.method}]${request.url}.`);
                        loaderUtilities.requestOverPort(innerPort, request)
                            .then((response)=>{
                                console.log(`Forwarding requested value "${response}" for [${request.method}]${request.url}`);
                                newPort.postMessage(response);
                                newPort.close();
                            });
                    };
                    window.parent.postMessage(outerChannel.port2, '*', [outerChannel.port2]);
                });//handle tool-load error?

    
  </head>
  <body></body>
</html>

www.client.com/client.html

<!DOCTYPE html>
<html>
  <head>
    <title>The stupidest client.</title>
    <meta charset="UTF-8">
    
      const pipe = new MessageChannel();
      const work = (request)=>{
          return "555";
      };
      pipe.port1.onmessage = (e)=>{
          const newPort = e.data.port;
          const request = e.data.resource;
          const response = work(request);
          console.log(`Receipt requested for [${request.method}]${request.url}; returning "${response}"`);
          newPort.postMessage(response);
          newPort.close();
      };
      window.parent.postMessage(pipe.port2, '*', [pipe.port2]); //should I be more restrictive of the recipient?
    
  </head>
  <body</body>
</html>

Comments

  • The above works, in the sense that the image loads and the request for that image has the correct custom header.
  • The Client presented is just a test rig that always returns ‘555’.

Question

Mostly I’m concerned with the security and usability of the Shim.

  • Is the usage of iframes, Promises, indexedDB, and inter-frame messaging trustworthy? Am I doing them correctly?
  • Does this Shim system provide the protections described in “Parties, Prerogatives, and Restrictions”, insofar as any modern web-browser is secure?
  • How should I approach error-handling?
  • What else should I do to test this system?


Get this bounty!!!

#StackBounty: #ruby-on-rails #cors #ruby-on-rails-5 Rails Font CORS policy

Bounty: 50

I can’t load this font for a CORS Policy.

Folder: app/assets/fonts/Inter-UI.var.woff2

<%=preload_link_tag("Inter-UI.var.woff2", as:'font', crossorigin: "anonymous")%>

Error:

Access to font at
http://localhost:3000/assets/Inter-UI.var-e2e323d19d24946c4d481135af27ba00f3266aa9d4abe4262e97088feccb6ca4.woff2
from origin ‘http://0.0.0.0:3000‘ has been blocked by CORS policy: No
‘Access-Control-Allow-Origin’ header is present on the requested
resource.

Response HTTP status code

enter image description here

If I go directly to http://localhost:3000/assets/Inter-UI.var-e2e323d19d24946c4d481135af27ba00f3266aa9d4abe4262e97088feccb6ca4.woff2 I can download the file successfully.

I have already tried with rack-cors gem, but it’s not working

config/environments/developement.rb

Rails.application.configure do

  config.middleware.insert_before 0, Rack::Cors do
    allow do
      origins '*'
      resource '*', :headers => :any, :methods => :any
    end
  end


Get this bounty!!!

#StackBounty: #node.js #reactjs #express #cors #axios cors failing for subdomains

Bounty: 50

CORS not working properly for domain and subdomain.
I’ve a single NodeJS server hosted at https://api.example.com
I’ve two ReactJS Clients

Client 1. https://example.com

Client 2. https://subdomain.example.com

I’ve configured Client 1 to force www so that a single origin is used for Client 1.
When I open Clien1 that works fine.
When I open Client2 I get the error that

Access to XMLHttpRequest at ‘https://api.example.com/someAPI‘ from origin ‘https://subdomain.example.com‘ has been blocked by CORS policy: The ‘Access-Control-Allow-Origin’ header has a value ‘https://www.example.com‘ that is not equal to the supplied origin

When I press Ctrl+F5 then it works fine but after that If I refresh Client1 then the error comes at Client1

Access to XMLHttpRequest at ‘https://api.example.com/someAPI‘ from origin ‘https://www.example.com‘ has been blocked by CORS policy: The ‘Access-Control-Allow-Origin’ header has a value ‘https://subdomain.example.com‘ that is not equal to the supplied origin.

Now When I press Ctrl+F5 at Client1 then it works fine but the same error goes to the Client2.

My nodeJS server is configured as follows

var whitelist = ['https://www.example.com', 'https://subdomain.example.com'];
    getCorsCoptions(req, callback) {
    var getOriginValue = () => {
      if (whitelist.indexOf(req.header('origin')) !== -1) {
        return true;
      } else {
        log.error(__filename, 'Not allowed by CORS', origin);
        return false;
      }
    }
    var corsOptions = {
      origin: getOriginValue(),
      methods: ['GET', 'POST'],
      credentials: true,
      preflightContinue: false,,
      allowedHeaders:["Content-Type","X-Requested-With","X-HTTP-Method-Override","Accept"]
    }
    callback(null, corsOptions)
  }

app.use('*', cors(this.getCorsCoptions));
app.options('*', cors(this.getCorsCoptions));

I’m using axios at React Client sides as follows

get(url: string) {
    return Axios.get(url, {
      withCredentials: true
    }).then((res: any) => {
      if (res) {
        return res.data;
      }
      return {};
    });
}

post(url: string, data: any) {
    let requestHeaders = { 'Content-Type': 'application/json' };
    return Axios.post(url, data, {
      headers: requestHeaders
      , withCredentials: true
    }).then((res: any) => {
      if (res) {
        return res.data;
      }
      return {};
    });
}


Get this bounty!!!

#StackBounty: #javascript #node.js #cors #xmlhttprequest #axios axios onUploadProgress and onDownloadProgress not working with CORS

Bounty: 500

I have a server written in Node.js, and a web client running in the browser. The client shall upload and download some files from and to the server. The server is not the one that originally delivers the client, so we have a cross-domain situation here.

The server uses the cors middleware to enable cross-domain requests. Since I am also using a number of custom headers, I use it with a configuration like this:

api.use(cors({
  origin: '*',
  allowedHeaders: [ 'content-type', 'authorization', 'x-metadata', 'x-to' ],
  exposedHeaders: [ 'content-type', 'content-disposition', 'x-metadata' ]
}));

In contrast, the client is using axios, and the code to upload a file looks like this:

await axios({
  method: 'post',
  url: 'https://localhost:3000/api/v1/add-file',
  data: content,
  headers: {
    // ...
  }
});

So far, everything works as expected, and it especially works with the CORS thing.

Now I would like to track the progress of the upload, and hence I add the onUploadProgress callback to the axios call, as suggested by its documentation:

await axios({
  method: 'post',
  url: 'https://localhost:3000/api/v1/add-file',
  data: content,
  headers: {
    // ...
  },
  onUploadProgress (progressEvent) {
    console.log({ progressEvent });
  }
});

If I now run the client, the upload still works – but the onUploadProgress callback is never called. I have read that not all browsers support this (internally this just binds to the onprogress event of the underlying XmlHttpRequest object), but latest Chrome should be able to do it (and if I try a different setup, where CORS is not involved, everything seems to work). So I assume that it’s a CORS issue.

I have read that as soon as you add a listener to the onprogress event, a preflight request will be sent. This in turn means that the server has to accept OPTIONS requests. For that, I added the following code on the server, before the aforementioned call to api.use:

api.options('*', cors({
  origin: '*',
  methods: [ 'GET', 'POST' ],
  allowedHeaders: [ 'content-type', 'authorization', 'x-metadata', 'x-to' ],
  exposedHeaders: [ 'content-type', 'content-disposition', 'x-metadata' ],
  optionsSuccessStatus: 200
}));

The result: The upload still works, but still no onUploadProgress event is raised. So I assume that I am missing something, related to CORS. The question is just: What am I missing?

I have also tried to re-run the same setup with a larger file (almost 2 GBytes instead of a few KBytes), but the result is the same. Does anyone have an idea what I am doing wrong here?


Get this bounty!!!