DEV Community

Palash Bauri πŸ‘»
Palash Bauri πŸ‘»

Posted on

Is it safe to use Google APIs from Client-Side Javascript πŸ€”

Today I was experimenting with APIs , and landed on https://developers.google.com/drive/api/v3/quickstart/js while learning about Google Drive API. After reading the code example on that page , I was concerned about exposing my API Key and Client ID to public.

<!DOCTYPE html>
<html>
  <head>
    <title>Drive API Quickstart</title>
    <meta charset="utf-8" />
  </head>
  <body>
    <p>Drive API Quickstart</p>

    <!--Add buttons to initiate auth sequence and sign out-->
    <button id="authorize_button" style="display: none;">Authorize</button>
    <button id="signout_button" style="display: none;">Sign Out</button>

    <pre id="content" style="white-space: pre-wrap;"></pre>

    <script type="text/javascript">
      // Client ID and API key from the Developer Console
      var CLIENT_ID = '<YOUR_CLIENT_ID>';
      var API_KEY = '<YOUR_API_KEY>';

      // Array of API discovery doc URLs for APIs used by the quickstart
      var DISCOVERY_DOCS = ["https://www.googleapis.com/discovery/v1/apis/drive/v3/rest"];

      // Authorization scopes required by the API; multiple scopes can be
      // included, separated by spaces.
      var SCOPES = 'https://www.googleapis.com/auth/drive.metadata.readonly';

      var authorizeButton = document.getElementById('authorize_button');
      var signoutButton = document.getElementById('signout_button');

      /**
       *  On load, called to load the auth2 library and API client library.
       */
      function handleClientLoad() {
        gapi.load('client:auth2', initClient);
      }

      /**
       *  Initializes the API client library and sets up sign-in state
       *  listeners.
       */
      function initClient() {
        gapi.client.init({
          apiKey: API_KEY,
          clientId: CLIENT_ID,
          discoveryDocs: DISCOVERY_DOCS,
          scope: SCOPES
        }).then(function () {
          // Listen for sign-in state changes.
............. βœ‚οΈ ...........
Enter fullscreen mode Exit fullscreen mode

So, Is it really safe to use code like this in public facing production webapps? πŸ€”πŸ€”

Top comments (9)

Collapse
 
tiguchi profile image
Thomas Werner • Edited

This is a common problem with client applications. The same problem also affects mobile apps. Consider API keys and other secrets not to be secret anymore when you decide to bundle them with your client apps. See also: developer.okta.com/blog/2019/01/22...

However, when you set up Google Drive API credentials for the first time Google guides you through a small questionnaire and also asks you how and from where you want to access their APIs:

Screenshot of Google API credentials setup wizard

One of those options is "Web browser (JavaScript)". When you select that option the only credential you are offered is the client ID, which is not a secret. It's basically just a unique identifier for your application. You can safely embed that in your JS code. Nothing to worry about.

Have you tried that instead? Does it work in your case?

Other than that, just in principle when you're dealing with 3rd party API keys and secrets, your best bet is to run your own web server and web API, and let that talk to 3rd party APIs on behalf of your client app.

That way you don't expose your secrets. However, it's up to you to implement your own auth schemes in order to limit and guard access to your own web API.

In case of Google Drive access, you're basically asking your end users to trust your app with their confidential documents. Here it's a bit more reassuring to let your client app do the talking directly with Google's API, and not through your service as a man in the middle.

Collapse
 
vamsikrish profile image
Vamsi Krishna

I wouldn't use SECRETS on the Client-Side since they risk exposing for the end-users. You can write a wrapper in Back-End stack like nodejs, c# etc and call those from your front-end and allow only your particular origin to that wrapper api and makesure your wrapper api has authentication so people cant bypass CORS from postman and such things.

Collapse
 
exadra37 profile image
Paulo Renato • Edited

so people cant bypass CORS from postman and such things.

Unfortunately they can be bypassed:

medium.com/netscape/hacking-it-out...

In my case, I forked the repo and added a configuration option that allows you to spoof the Origin header, which defaults to true. You can check it out here. I also hosted this on Heroku as cors-escape.herokuapp.com (nothing to see there πŸ˜€).

So basically you cannot spoof it directly with JavaScript in the browser, but you can do it in a programmatically way.

Regarding Postman I am not aware if it allows or not to fake the origin header.

The lesson to take from here is that the CORS is a good protection on the browser side but not that hard to bypass.

The WHO vs WHAT Is Accessing the API Server

A usual misconception among developers it's WHO vs WHAT is accessing the API server.

For a better understanding off the difference between WHO vs WHAT is accessing your API server, I recommend you to read this section of my article, but I will extract here some lines of it:

The who is the user of the mobile app that we can authenticate, authorize and identify in several ways, like using OpenID Connect or OAUTH2 flows.

The what is the thing making the request to the API server. Is it really a genuine instance of your mobile app, or is a bot, an automated script or an attacker manually poking around your API server with a tool like Postman?

Without going into more detail, I want to say that any API server will have a hard time to figure out the WHAT bit, that will misuse and abuse of WHO the API server thinks is talking with.

Collapse
 
vamsikrish profile image
Vamsi Krishna • Edited

I know we can bypass CORS from tools like postman since it is browser based protection only so I wanted to say to add authentication since cors will be bypassed from other means of making calls. Maybe my framing of sentence made you think I was saying cors can’t be bypassed

Thread Thread
 
exadra37 profile image
Paulo Renato

Maybe my framing of sentence made you think I was saying cors can’t be bypassed

Yes, it made me thought that you believed that CORS was enough to protect the API.

Collapse
 
richardjecooke profile image
RichardJECooke

Yes it's safe. You aren't exposing any secret key. And your app key is linked to your domain (myapp.com or whatever). So a hacker can create a malicious duplicate of your app only if they also serve it from your domain, which they can't.

Collapse
 
holdyourwaffle profile image
HoldYourWaffle • Edited

This is not true at all.

API_KEY should definitely be kept secret. The "domain check" only works inside a browser and is easily spoofed by tools like Postman.

The reference OP provided explicitly states that this method is not suited for production:

This quickstart uses a simplified authentication approach that is appropriate for a testing environment. For a production environment, we recommend learning about authentication and authorization before choosing the access credentials that are appropriate for your app.

Collapse
 
richardjecooke profile image
RichardJECooke

As I understand it, both the id/key are just ways of identifying the user to Google (developers.google.com/maps/premium...).

In any situation if you can put an identifier on the server, so that all calls from the browser (client) have to go through the server and the identifier is kept secret from the browser, that's great. But some apps don't have a server - they are purely client side (browser based) and in that case you have to put your identifiers for your app somewhere in the app's code. There's no choice.

Collapse
 
sneezeaway profile image
Garyyyyyyy

I think it’s always good to put your API keys in .env file or other places. Safety concerns if you put it in the client side.