You are viewing limited content. For full access, please sign in.

Discussion

Discussion

Google API and a ChatGPT rabbit hole - any Google API users out there that can confirm this is factual?

posted on January 5

I recently updated my Google Calendar Events script to use the newest GIS authentication method required for newly created apps on Google Dev Console and it was working great. I built the test form sometime late last year.

Just went back in to test it and it was not working, throwing an error about Cross Origin when trying to authenticate.

I asked ChatGPT why I was getting this error now and it said

When CORP* is present, Chrome automatically applies popup isolation rules that can break Google OAuth popups in certain contexts (iframes, redirects, or strict sites).

This is a Chrome behavior change, not your code.

👉 Result:
Google Identity popup can’t communicate back → authorization fails.

Why it worked before

Chrome recently tightened enforcement around:

• CORP
• popups
• embedded contexts

Older Chrome versions were more permissive.
No code changes required for this to start failing.

*CORP is short for Cross-Origin-Resource-Policy and this is in the headers of Laserfiche Forms on LF Cloud.

Assuming Google really did make this change very recently and break the authentication to their own API for scripts that use the pop up method of authentication (the most convenient method), I switched to using the redirect method where it continued to fail to authenticate.

ChatGPT had this to say about my redirect URL

That page is not a true top-level navigation target

app.laserfiche.com/forms/... is a hosted application shell, not a standalone page.

OAuth redirect requires:

Full page navigation

Top-level window

Ability to receive and parse ?code=...

Laserfiche Forms does not expose a place for you to run OAuth callback logic.

So Google redirects → Laserfiche → Laserfiche swallows the code → page reloads.

That’s exactly what you’re seeing.

🧭 What this means (important)

You CANNOT complete Google OAuth directly inside a Laserfiche Form.

Not with:

Popup

Redirect

GIS

Legacy gapi

Any workaround

This is a platform limitation, not your code.

This leaves me in an impossible situation. I am not sure what to believe here. This would mean Google (and Microsoft for that matter because Edge is doing the same thing) just implemented a change that breaks the ability for any Laserfiche users to use the new GIS Auth method and there is no option to create accounts that work with the old auth method anymore, only my original accounts created in 2016 work now.

0 0
replied on January 5

I'm unaware of the CORP stuff, but redirect flows should work with forms. The adobe sign integration solution template uses this approach to handle OAUTH logins. basically follows:

  1. Open form for the first time
  2. Open auth link, etc
  3. login
  4. redirect back to the same form with url auth_token="xyz"
  5. make sure the form has a field with variable name matching the url parameter (auth_token in this case)
  6. don't open auth link when the variable exists and instead run authorization code

 

In the google console you should be able to add the form link as an allowed redirect url.

1 0
replied on January 7

So it is true that modern browsers just recently blocked pop up authentication?

Using redirect from Cloud does not work in our system. It says canceled for the status when you reach the redirect page and sends you right back without asking to authenticate.

Under the response tab it always fails to load the response data so we can not see why they sent us back without authorizing us first.

We do have the redirect URL allowed in the console

Here is the payload from the canceled request

 

0 0
replied on January 7

you wont be able to open the authentication via a popup as far as I can tell. You should just open the authentication in a new tab instead of a new window.

0 0
replied two days ago

I am not sure we have a choice in how the authentication site opens. We are creating the following object and then requesting a code and somehow the browser just visits the authentication site, which then kicks us back to our redirect URL.

codeClient = google.accounts.oauth2.initCodeClient({
  client_id: CLIENT_ID,
  scope: 'https://www.googleapis.com/auth/calendar.events',
  ux_mode: 'redirect',
  redirect_uri: 'https://app.laserfiche.com/forms/HfDuh',
});

codeClient.requestCode();

 

0 0
replied two days ago

It could be a content security policy on the LF side, or because the redirect is not "user initiated" meaning it didn't happen from a button click.

 

Is there an error in the dev console when you initiate the client?

0 0
replied two days ago

The console remains completely empty. The only error is in the network tab showing a status of (canceled) with a red X

0 0
replied two days ago

Ok, well it very likely could be the CSP then. This doc page talks about this header (#5), try adding the googleapis domain as shown in the docs and try again.

0 0
replied two days ago

This is for Laserfiche Cloud. Is there a way to change headers in Cloud? If so I would just change the Cross Origin Opener Policy to be same-origin-allow-popups as the Google Documentation recommends in order to allow pop up auth.

https://developers.google.com/identity/gsi/web/guides/get-google-api-clientid?utm_source=chatgpt.com

0 0
replied two days ago

What is your full code? I just did the following and it redirects to google's auth page just fine

 

$(() => {
  $.getScript('https://apis.google.com/js/api.js', function() {
      $.getScript('https://accounts.google.com/gsi/client', function() {
        const codeClient = google.accounts.oauth2.initCodeClient({
          client_id: '12345',
          scope: 'https://www.googleapis.com/auth/calendar.events',
          ux_mode: 'redirect',
          redirect_uri: 'https://app.laserfiche.com/forms/HfDuh',
        });
        console.log(codeClient);
        codeClient.requestCode();
      });
  });
});

 

0 0
replied two days ago • Show version history

Below is my full code block in a custom html activity. Credentials are removed.

Am I missing an error somewhere in the network tab? I just see the red X and the (canceled) status. If I mouse over the red X or try to click on it I don't get anything.

The commented out tokenClient variable references were the pop up method, the codeClient variable references are the new redirect method.

<div>
  <button id="authorizeBtn">Authorize</button>
  <button id="createEventBtn">Create Event</button>
</div>
    <label>Subject: <input type="text" id="evtSubject"></label><br>
    <label>Date (YYYY-MM-DD): <input type="date" id="evtDate"></label><br>
    <label>Start Time (HH:MM): <input type="time" id="evtStartTime"></label><br>
    <label>End Time (HH:MM): <input type="time" id="evtEndTime"></label><br>
  
<script>
(async function() {
  // === CONFIGURATION ===
  const CLIENT_ID = 'X';
  const API_KEY = 'Y';
  const DISCOVERY_DOC = 'https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest';
  const SCOPES = 'https://www.googleapis.com/auth/calendar.events';

  let codeClient;
  let tokenClient;
  let gapiInited = false;
  let gisInited = false;

  // === LOAD THE GOOGLE API CLIENT ===
  function loadGapi() {
    return new Promise((resolve, reject) => {
      const script = document.createElement('script');
      script.src = 'https://apis.google.com/js/api.js';
      script.onload = resolve;
      script.onerror = reject;
      document.body.appendChild(script);
    });
  }

  // === LOAD THE GOOGLE IDENTITY SERVICES LIBRARY ===
  function loadGIS() {
    return new Promise((resolve, reject) => {
      const script = document.createElement('script');
      script.src = 'https://accounts.google.com/gsi/client';
      script.onload = resolve;
      script.onerror = reject;
      document.body.appendChild(script);
    });
  }

  // === INIT GOOGLE API CLIENT ===
  async function initGapiClient() {
    await gapi.client.init({
      apiKey: API_KEY,
      discoveryDocs: [DISCOVERY_DOC],
    });
    gapiInited = true;
    maybeEnableButtons();
  }

  // === INIT GOOGLE IDENTITY TOKEN CLIENT ===
  function initGisClient() {
/*
    tokenClient = google.accounts.oauth2.initTokenClient({
      client_id: CLIENT_ID,
      scope: SCOPES,
      callback: (tokenResponse) => {
          console.log('test');
        if (tokenResponse.access_token) {
          document.getElementById('createEventBtn').disabled = false;
       console.log('create event enabled');
        }else{
       console.log('create event NOT enabled');

        }
      },
    });
*/
codeClient = google.accounts.oauth2.initCodeClient({
  client_id: CLIENT_ID,
  scope: 'https://www.googleapis.com/auth/calendar.events',
  ux_mode: 'redirect',
  redirect_uri: 'https://app.laserfiche.com/forms/HfDuh',
});
    gisInited = true;
    maybeEnableButtons();
  }

  function maybeEnableButtons() {
    if (gapiInited && gisInited) {
      document.getElementById('authorizeBtn').disabled = false;
    }
  }

  // === AUTHORIZE BUTTON HANDLER ===
  document.getElementById('authorizeBtn').onclick = () => {
    //tokenClient.requestAccessToken({ prompt: 'consent'});
    codeClient.requestCode();
  };

  // === CREATE EVENT FUNCTION ===
  async function createCalendarEvent(subject, dateStr, startTimeStr, endTimeStr) {
    const startDateTime = new Date(`${dateStr}T${startTimeStr}:00`);
    const endDateTime = new Date(`${dateStr}T${endTimeStr}:00`);
    
    const event = {
      summary: subject,
      start: { dateTime: startDateTime.toISOString() },
      end: { dateTime: endDateTime.toISOString() },
    };

    const request = gapi.client.calendar.events.insert({
      calendarId: 'primary',
      resource: event,
    });

    const response = await request;
    console.log('Event created:', response.result);
    alert('Event created successfully!');
  }

  document.getElementById('createEventBtn').onclick = async () => {
    await createCalendarEvent('Test Event', '2025-10-28', '14:00', '15:00');
  };

  // === LOAD LIBRARIES IN ORDER ===
  await loadGapi();
  await new Promise((resolve) => gapi.load('client', resolve));
  await initGapiClient();

  await loadGIS();
  initGisClient();

})();
</script>

 

0 0
replied two days ago

Is that AI generated code? I couldn't get that code to work, but I got the code from the docs to work fine

https://developers.google.com/workspace/calendar/api/quickstart/js

0 0
replied two days ago

Their official code works for you? I moved away from it to use a chatGPT version when I started getting the following error. I reviewed their code to make sure there is no append function on the variable CLIENT_ID which is set as a constant to begin with. It should just be holding a string of characters. Because this error was completely illogical and the chatGPT version worked I switched.

 

0 0
replied two days ago

That error doesn’t happen when you preview the form. It happens in the designer because it keeps reinstantiating the code so it tries to redecorate the variable

0 0
replied two days ago

For me I only see the error in the console after choosing to preview, but before I authenticate. I can click authenticate and the window pops up allowing me to choose my account, but it still displays this message in the console after I finish trying to authenticate and it does not show the add calendar button.

0 0
replied two days ago • Show version history

I was able to get a list of events from my calendar. It did throw errors and i'm not entirely sure why. It does seem to be instantiating twice erroring on the CLIENT_ID variable you mentioned too as well as the CORP error. But as you can see below, I was able to get it to work.

NOTE: I made the custom buttons type="button" because it was otherwise submitting the form

<p>Google Calendar API Quickstart</p>

<!--Add buttons to initiate auth sequence and sign out-->
<button id="authorize_button" type="button" onclick="handleAuthClick()">Authorize</button>
<button id="signout_button" type="button" onclick="handleSignoutClick()">Sign Out</button>

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

<script type="text/javascript">
  /* exported gapiLoaded */
  /* exported gisLoaded */
  /* exported handleAuthClick */
  /* exported handleSignoutClick */

  // TODO(developer): Set to client ID and API key from the Developer Console
  const CLIENT_ID = 'CODE';
  const API_KEY = 'KEY;

  // Discovery doc URL for APIs used by the quickstart
  const DISCOVERY_DOC = 'https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest';

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

  let tokenClient;
  let gapiInited = false;
  let gisInited = false;

  document.getElementById('authorize_button').style.visibility = 'hidden';
  document.getElementById('signout_button').style.visibility = 'hidden';

  /**
   * Callback after api.js is loaded.
   */
  function gapiLoaded() {
    gapi.load('client', initializeGapiClient);
  }

  /**
   * Callback after the API client is loaded. Loads the
   * discovery doc to initialize the API.
   */
  async function initializeGapiClient() {
    await gapi.client.init({
      apiKey: API_KEY,
      ux_mode: 'redirect',

      discoveryDocs: [DISCOVERY_DOC],
    });
    gapiInited = true;
    maybeEnableButtons();
  }

  /**
   * Callback after Google Identity Services are loaded.
   */
  function gisLoaded() {
    tokenClient = google.accounts.oauth2.initTokenClient({
      client_id: CLIENT_ID,
      scope: SCOPES,
      ux_mode: 'redirect',
      callback: '', // defined later
    });
    gisInited = true;
    maybeEnableButtons();
  }

  /**
   * Enables user interaction after all libraries are loaded.
   */
  function maybeEnableButtons() {
    if (gapiInited && gisInited) {
      document.getElementById('authorize_button').style.visibility = 'visible';
    }
  }

  /**
   *  Sign in the user upon button click.
   */
  function handleAuthClick() {
    tokenClient.callback = async (resp) => {
      if (resp.error !== undefined) {
        throw (resp);
      }
      document.getElementById('signout_button').style.visibility = 'visible';
      document.getElementById('authorize_button').innerText = 'Refresh';
      await listUpcomingEvents();
    };

    if (gapi.client.getToken() === null) {
      // Prompt the user to select a Google Account and ask for consent to share their data
      // when establishing a new session.
      tokenClient.requestAccessToken({ prompt: 'consent' });
    } else {
      // Skip display of account chooser and consent dialog for an existing session.
      tokenClient.requestAccessToken({ prompt: '' });
    }
  }

  /**
   *  Sign out the user upon button click.
   */
  function handleSignoutClick() {
    const token = gapi.client.getToken();
    if (token !== null) {
      google.accounts.oauth2.revoke(token.access_token);
      gapi.client.setToken('');
      document.getElementById('content').innerText = '';
      document.getElementById('authorize_button').innerText = 'Authorize';
      document.getElementById('signout_button').style.visibility = 'hidden';
    }
  }

  /**
   * Print the summary and start datetime/date of the next ten events in
   * the authorized user's calendar. If no events are found an
   * appropriate message is printed.
   */
  async function listUpcomingEvents() {
    let response;
    try {
      const request = {
        'calendarId': 'primary',
        'timeMin': (new Date()).toISOString(),
        'showDeleted': false,
        'singleEvents': true,
        'maxResults': 10,
        'orderBy': 'startTime',
      };
      response = await gapi.client.calendar.events.list(request);
    } catch (err) {
      document.getElementById('content').innerText = err.message;
      return;
    }

    const events = response.result.items;
    if (!events || events.length == 0) {
      document.getElementById('content').innerText = 'No events found.';
      return;
    }
    // Flatten to string to display
    const output = events.reduce(
      (str, event) => `${str}${event.summary} (${event.start.dateTime || event.start.date})\n`,
      'Events:\n');
    document.getElementById('content').innerText = output;
  }
</script>
<script async="" defer="" src="https://apis.google.com/js/api.js" onload="gapiLoaded()"></script>
<script async="" defer="" src="https://accounts.google.com/gsi/client" onload="gisLoaded()"></script>

 

1 0
replied two days ago

I just tried your exact code, but with my creds. It is actually re-throwing the error about CLIENT_ID each time I click Authorize. The popup comes up as it always has for me, I choose to continue with my account and it closes without error, but notice how no sign out button shows up and nothing is listed in the content box with no failures in the console.

0 0
replied two days ago

Check your buttons, I forgot I made a change there they're type="button" otherwise they are actually submitting the form. its hard to tell but the form is refreshing when you click authorize.

1 0
replied two days ago

Hey it worked! I would rather use the official script for sure, as I have used it for 10 years now. Only recently did I switch after it stopped working and I could not work through it, as of last year. But now it looks good, so I will try to work with this. Thank you!

And the pop up is working! Which answered my original question if it really is true that a pop up is impossible with modern browsers on sites that don't have the same-origin-allow-popups Cross Origin Header.

1 0
replied two days ago

Ya I could not get it to NOT do the popup haha. I might try to get this to work in the modern designer by empower too.

0 0
replied two days ago

Was going to try tackling the modern designer version next, but I think there might be some restrictions on the Custom HTML objects in that designer that could be a problem if I remember right.

0 0
You are not allowed to follow up in this post.

Sign in to reply to this post.