In the previous post we deployed the web app to Firebase Hosting. Let’s address the issue with same-origin policy that is preventing access to the Bosch eBike System APIs https://api.bosch-ebike.com.

One way of doing this is using a Firebase Function that acts as a proxy. First let’s call firebase init again to set up functions.

During the project setup we answer the questions as follows:

  • Which Firebase features do you want to set up for this directory? Functions
  • Please select an option Use an existing project
  • Select a default Firebase project for this directory open-ebike (Open eBike)
  • What language would you like to use to write Cloud Functions? JavaScript
  • Do you want to use ESLint to catch probable bugs and enforce style? N
  • Do you want to install dependencies with npm now? Y

Now let’s add a Firebase function that proxies API calls in functions/index.js. The function will take the original request - including the query parameters and the auth header and will forward it to https://api.bosch-ebike.com. In return the response will be returned to the frontend calling the function.

Note: the original request contains the path section /api which we need to remove here.

const functions = require("firebase-functions");
const axios = require("axios");
const cors = require("cors")({ origin: true });

// Define the target API base URL
const targetApiUrl = "https://api.bosch-ebike.com";

exports.proxyApi = functions.https.onRequest((request, response) => {
  cors(request, response, async () => {
    // Construct the full URL for the external API, including the path and query string
    const fullUrl = `${targetApiUrl}${request.path.replace("/api", "")}${request.url.includes("?") ? "?" + request.url.split("?")[1] : ""}`;

    // Extract the bearer token from the request headers
    const authHeader = request.headers.authorization;
    if (!authHeader) {
      return response.status(401).send("Authorization header is missing");
    }

    try {
      // Create a config object for the axios request
      const axiosConfig = {
        method: request.method,
        url: fullUrl,
        headers: {
          Authorization: authHeader,
        },
        // Forward the request body if it exists
        data: request.body,
      };

      // Make the dynamic request to the target API
      const apiResponse = await axios(axiosConfig);

      // Forward the API's response to the client
      response.status(apiResponse.status).send(apiResponse.data);
    } catch (error) {
      console.error("API proxy error:", error);
      // Forward the error response from the API to the client
      if (error.response) {
        response.status(error.response.status).send(error.response.data);
      } else {
        response.status(500).send("Internal Server Error");
      }
    }
  });
});

The function can be deployed by running the following command:

firebase deploy --only functions

In the Firebase Console we can now see our function.

firebase-function.png

The only thing left to do is to configure our app to call the Firebase function instead of the actual API endpoint. We can do so by adding this rewrite in firebase.json that forwards all calls to /apito our function.

{
  "source": "/api/**",
  "function": "proxyApi"
}

To actually call /api we need to adjust the value of ebikeApiUrl in src/environments/environment.firebase-hosting.ts.

export const environment = {
  eBikeApiUrl: '/api'
}

For this approach to work properly need to allow or function to be accessed publicly. We can so via the Cloud Run section inside the Google Cloud Connsole. Here we can see our deployed function.

cloud-run-overview.png

In the function’s details page, in the Security tab we need to select Allow public access.

cloud-run-allow-public-access.png


In the next post we will make the client ID configurable so that each user can use their individual client application which is needed to access their own data.