# Google Workspace

{% hint style="info" %}
This feature is available in the **Premium plan and higher**.
{% endhint %}

{% hint style="danger" %}
**Remember to** [**grant your Google users access permissions**](#step-4-managing-user-access) **to GoodAccess. Users without them won't be able to log in.**
{% endhint %}

## Step 1 - Adding a new identity provider

[Log in to the GoodAccess **Control Panel**, and go to **Settings** > **SSO & Identity**.](https://app.goodaccess.com/sso-and-identity/)

Click **+ Add provider**, enter the **Provider name**, choose your **Identity Provider**, and click **Continue**.

## Step 2 - Setting up Single Sign-On with SAML

Log in to the [Google Admin console](https://admin.google.com/), and go to [Apps > Web and mobile apps](https://admin.google.com/ac/apps/unified).

Click **Add App**, and **Add custom SAML app**.

<figure><img src="https://418253935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiJ406Lpi9EKoWDi7GFL7%2Fuploads%2FuCr5ZOL9feYMfuIgi7X2%2FFeatures_SSO_Google_Workspace_01.png?alt=media&#x26;token=b8096b7f-29af-46fa-a763-048eea8c308b" alt="Google Admin console with key steps to adding a new custom SAML application."><figcaption><p>Adding a new custom SAML application</p></figcaption></figure>

### 1.  App details

Give the appplication a name, upload a logo, and click **Continue**.

<figure><img src="https://418253935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiJ406Lpi9EKoWDi7GFL7%2Fuploads%2Fr7GWFbGVRHfEGbrO8Bto%2FFeatures_SSO_Google_Workspace_02.png?alt=media&#x26;token=9e984def-81c6-4b27-8c7b-8970f378ef2f" alt="Google Admin console with key steps to setting up the &#x22;App details&#x22;."><figcaption><p>Setting up the App details</p></figcaption></figure>

### 2. Google Identity Provider details

Copy the details to GoodAccess - **(3) Identity Provider links**, and click **Continue**.

* **Sign in URL** - SSO URL
* **Entity ID** - Entity ID
* **X509 signing certificate** - Certificate

<figure><img src="https://418253935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiJ406Lpi9EKoWDi7GFL7%2Fuploads%2FQ4Y74CTp5vcSq8eSGiD1%2FFeatures_SSO_Google_Workspace_03.png?alt=media&#x26;token=89d2d562-2a0f-45c8-8960-571b09039853" alt="Google Admin console with key steps to setting up the &#x22;Google Identity Provider details&#x22;."><figcaption><p>Setting up the Google Identity Provider details</p></figcaption></figure>

### 3. Service provider details

Copy the details from GoodAccess - **(2) GoodAccess links**, and click **Continue**.

* **ACS URL** - Assertion Consumer Service URL
* **Entity ID** - Entity ID
* **Start URL** - Login URL
* **Name ID format** - UNSPECIFIED
* **Name ID** - Basic Information > Primary email

<figure><img src="https://418253935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiJ406Lpi9EKoWDi7GFL7%2Fuploads%2FKIJTJgoT12rDBwbru8kb%2FFeatures_SSO_Google_Workspace_04.png?alt=media&#x26;token=e74b9cf2-397d-4255-a452-bd1090a54cc0" alt="Google Admin console with key steps to setting up the &#x22;Service provider details&#x22;."><figcaption><p>Setting up the Service provider details</p></figcaption></figure>

### 4. Attribute mapping

Click **ADD MAPPING**, and add two attributes as follows:

| Google Directory attributes | App attributes           |
| --------------------------- | ------------------------ |
| Primary email               | "email" (without quotes) |
| First name                  | "name" (without quotes)  |

Click **Finish** to confirm your settings.

<figure><img src="https://418253935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiJ406Lpi9EKoWDi7GFL7%2Fuploads%2FVRXzc4zbJT5nrsNdtSr8%2FFeatures_SSO_Google_Workspace_05.png?alt=media&#x26;token=89275a9f-996a-43b4-8011-daa3c4d1a63f" alt="Google Admin console with key steps to setting up the &#x22;Attribute mapping&#x22;."><figcaption><p>Setting up Attribute mapping</p></figcaption></figure>

{% hint style="info" %}
If you want to set up SCIM, save the **Provider ID** for the next step, and click **Submit**.

If you don't want to set up SCIM, skip the next step in GoodAccess, and click **Submit** to finish the configuration.
{% endhint %}

You have now successfully set up your Google Workspace SSO with GoodAccess.

## Step 3 (optional) - Setting up SCIM using an API

Since SCIM for Google Workspace is not currently supported for public use, it's necessary to use a combination of Google Apps Script and GoodAccess API Integration for complete user management.

{% hint style="info" %}
The user provisioning time period depends on the [Trigger](#id-4.-creating-a-trigger-for-the-script) settings (default is 1 hour).
{% endhint %}

### 1. Creating a new API Integration

[In the GoodAccess **Control Panel**, go to **Settings** > **API Integration**.](https://app.goodaccess.com/api-integration/)

Create a new [API Integration](https://support.goodaccess.com/configuration-guides/features/api-integration) with the scopes specified below, and securely save the **Token** for the next step.

* Members
  * Create
  * Update
  * Remove
* Groups
  * Create
  * Update
  * Remove

### 2. Creating the Google Apps Script

Go to [Google Apps Script](https://script.google.com/home/start), and click **+** **New Project**.

Click **+** to **add a** **service**, and add **Admin SDK API**.

<figure><img src="https://418253935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiJ406Lpi9EKoWDi7GFL7%2Fuploads%2FYo9x9ej5f7Ujbaqb4mpi%2FFeatures_SSO_Google_Workspace_06.png?alt=media&#x26;token=5bfe2e4f-4621-4f47-96d1-4cb0042dad4a" alt="Google Apps Script project with key steps to adding Admin SDK API service."><figcaption><p>Adding Admin SDK API service</p></figcaption></figure>

Delete any placeholder code in the editor (e.g., `function myFunction() {...}`). Copy the following code snippet and paste it into the code editor:

{% hint style="danger" %}
**Configuration Required**

Before running the script, you must update the configuration variables at the top of the file:

1. **Mandatory replacements**

Replace these placeholders with your specific values:

* `<DOMAIN_NAME>` - The verified domain of your organization in Google Workspace (e.g. goodaccess.com).
* `<PROVIDER_ID>` - The Provider ID you obtained in the final step of the GoodAccess SSO configuration form.
* `<TOKEN>` - The Token you obtained when creating the GoodAccess API Integration.

2. **Define who gets synced (Sync Group)**

**How it works**: *Only* users belonging to the designated Sync Group are provisioned into GoodAccess. However, all other Google Workspace groups (unless excluded below) are still synchronized, and your imported users will automatically be assigned to them based on their Google Workspace memberships.

You have two options to set up the Sync Group:

* **Use the default setup**: Create a group in your Google Admin console named exactly `sync-to-goodaccess` and add the users you want to provision.
* **Use an existing group**: If you already have a group for this purpose (e.g., `vpn-users`), change the prefix in the `SYNC_GROUP_EMAIL` variable. For example, change `'sync-to-goodaccess@'` to `'vpn-users@'` .

3. **Optional filtering**

Review and modify this list to exclude specific groups from synchronization:

* `EXCLUDED_GROUPS` - Defines groups to ignore. You can use specific names (e.g., `ga-example`) or naming patterns with wildcards (e.g., `gcp-*` excludes any group starting with "gcp-").
  {% endhint %}

```php
/**
 * CONFIGURATION
 */
const DOMAIN            = '<DOMAIN_NAME>';       // The verified domain of your organization in Google Workspace (e.g. goodaccess.com)
const PROVIDER_ID       = '<PROVIDER_ID>';       // The Provider ID you obtained in the final step of the GoodAccess SSO configuration form
const INTEGRATION_TOKEN = '<TOKEN>';             // The Token you obtained when creating the GoodAccess API Integration

/**
 * SYNC GROUP:
 * Only users belonging to this specific group will be synchronized to GoodAccess.
 * Example: 'sync-to-goodaccess@yourdomain.com'
 */
const SYNC_GROUP_EMAIL  = 'sync-to-goodaccess@' + DOMAIN;

/**
 * EXCLUDED GROUPS:
 * List of group names or patterns to ignore. Supports '*' as a wildcard.
 * Examples: 'gcp-*', 'internal-testing', '*-temp'
 */
const EXCLUDED_GROUPS = [
  "gcp-*"
];

/**
 * MAIN FUNCTION
 * Orchestrates the synchronization process.
 */
function syncUsers() {
  // 1. Get IDs of all users who are members of the mandatory sync group
  const authorizedUserIds = getAuthorizedUserIds();
  
  if (authorizedUserIds.size === 0) {
    Logger.log('No users found in the sync group: ' + SYNC_GROUP_EMAIL);
    return;
  }

  // 2. Fetch detailed info for those authorized users
  const users = getAllAuthorizedUsers(authorizedUserIds);
  
  // 3. Fetch all groups and their members (filtered by exclusion list and authorization)
  const groups = getFilteredGroups(authorizedUserIds);

  // 4. Send data to GoodAccess
  sendRequest(users, groups);
}

/**
 * Retrieves a Set of user IDs that belong to the "sync-to-goodaccess" group.
 */
function getAuthorizedUserIds() {
  const authorizedIds = new Set();
  let pageToken;

  try {
    do {
      const response = AdminDirectory.Members.list(SYNC_GROUP_EMAIL, {
        pageToken: pageToken,
        maxResults: 200 // Maximum allowed by API for members
      });

      if (response.members) {
        response.members.forEach(member => {
          // We only care about users, not nested groups or customers
          if (member.type === 'USER') {
            authorizedIds.add(member.id);
          }
        });
      }
      pageToken = response.nextPageToken;
    } while (pageToken);
  } catch (e) {
    Logger.log('Error fetching sync group members: ' + e.message);
  }

  return authorizedIds;
}

/**
 * Fetches user details (email, name) but only for users in the authorized set.
 */
function getAllAuthorizedUsers(authorizedUserIds) {
  const members = {};
  let pageToken;

  do {
    const response = AdminDirectory.Users.list({
      domain: DOMAIN,
      pageToken: pageToken,
      maxResults: 500 // Increased limit for better performance
    });

    if (response.users) {
      response.users.forEach(user => {
        // Only include user if they are in the sync group
        if (authorizedUserIds.has(user.id)) {
          members[user.id] = {
            email: user.primaryEmail,
            name: user.name.fullName
          };
        }
      });
    }
    pageToken = response.nextPageToken;
  } while (pageToken);

  return members;
}

/**
 * Fetches all domain groups, filters them by exclusion patterns,
 * and includes only members who are also in the authorized sync group.
 */
function getFilteredGroups(authorizedUserIds) {
  const groups = {};
  let pageToken;

  do {
    const response = AdminDirectory.Groups.list({
      domain: DOMAIN,
      pageToken: pageToken,
      maxResults: 200
    });

    if (response.groups) {
      response.groups.forEach(group => {
        const groupEmail = group.getEmail();
        const groupName = groupEmail.split("@")[0];

        // Skip if group matches any excluded pattern
        if (isExcluded(groupName)) {
          return;
        }

        const members = getGroupMembers(groupEmail, authorizedUserIds);
        
        // Only add group if it has at least one authorized member
        if (members.length > 0) {
          groups[group.id] = {
            name: groupName,
            members: members
          };
        }
      });
    }
    pageToken = response.nextPageToken;
  } while (pageToken);

  return groups;
}

/**
 * Gets members of a specific group, filtered by the authorized user list.
 */
function getGroupMembers(groupEmail, authorizedUserIds) {
  let members = [];
  let pageToken;

  do {
    const response = AdminDirectory.Members.list(groupEmail, {
      pageToken: pageToken,
      maxResults: 200
    });

    if (response.members) {
      response.members.forEach(member => {
        // Only add member if they are a user and exist in the authorized sync group
        if (member.id != null && authorizedUserIds.has(member.id)) {
          members.push(member.id);
        }
      });
    }
    pageToken = response.nextPageToken;
  } while (pageToken);

  return members;
}

/**
 * Checks if a group name matches any of the excluded patterns (supports *).
 */
function isExcluded(groupName) {
  return EXCLUDED_GROUPS.some(pattern => {
    const regexPattern = pattern.replace(/\*/g, '.*');
    const regex = new RegExp("^" + regexPattern + "$", "i");
    return regex.test(groupName);
  });
}

/**
 * Sends the collected data to the GoodAccess API.
 */
function sendRequest(users, groups) {
  const apiUrl = 'https://integration.goodaccess.com/api/v2/google-workspace/sync-users';
  
  const payload = {
    'domain': DOMAIN,
    'users': users,
    'groups': groups,
    'provider_id': PROVIDER_ID
  };
  
  const options = {
    'method': 'post',
    'contentType': 'application/json',
    'headers': {
      'authorization': INTEGRATION_TOKEN
    },
    'payload': JSON.stringify(payload),
    'muteHttpExceptions': true
  };
  
  try {
    const response = UrlFetchApp.fetch(apiUrl, options);
    if (response.getResponseCode() == 200) {
      Logger.log('The API request was successfully sent.');
    } else {
      Logger.log("Error sending API request. Code: " + response.getResponseCode() + " Body: " + response.getContentText());
    }
  } catch (e) {
    Logger.log("Critical error in UrlFetchApp: " + e.message);
  }
}
```

### 3. Authorizing the script

Before automating the synchronization, you must save the script and run it manually once to grant the necessary permissions.

Click the **Save project** icon, and ensure the function **syncUsers** is selected.

Click the **Run** button, and click **Review permissions**.

{% hint style="danger" %}
You might see a screen asking to "Select what \[Project Name] can access".

Ensure you **check the "Select All" box** to grant all the required permissions (view groups, see users, connect to external service).

If you instead see a "Google hasn't verified this app" warning, click **Advanced** and then **Go to \[Project Name] (unsafe)**.
{% endhint %}

Click **Continue** or **Allow**.

Wait for the execution log to start. If configured correctly, you should see "Execution started" and "Execution completed" at the bottom of the screen.

<figure><img src="https://418253935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiJ406Lpi9EKoWDi7GFL7%2Fuploads%2FbRisRUbugYvoYzYC4Qsj%2FFeatures_SSO_Google_Workspace_07.png?alt=media&#x26;token=68aa948f-721c-47f2-8f07-15083db0a5c2" alt="Google Apps Script project with key steps to authorizing the script."><figcaption><p>Authorizing the script</p></figcaption></figure>

### 4. Creating a trigger for the script

In the left menu, go to **Triggers**, and click **+ Add Trigger**.

* Choose which function to run - **syncUsers**
* Choose which deployment should run - **Head**
* Select event source - **Time-driven**
* Select type of time based trigger - **Hour timer**
* Select hour interval - **Every hour**

Click **Save**.

<figure><img src="https://418253935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiJ406Lpi9EKoWDi7GFL7%2Fuploads%2FybTHnDVzqfdjP1euPcFs%2FFeatures_SSO_Google_Workspace_08.png?alt=media&#x26;token=46015e0d-157d-419c-bcd5-eed1479bdd1f" alt="Google Apps Script project with key steps to creating a trigger for the script."><figcaption><p>Creating a trigger for the script</p></figcaption></figure>

You have now successfully set up Google Workspace SCIM with GoodAccess.

## Step 4 - Managing user access

In the application click **User access**.

Choose who should have access, select **ON**, and click **Save**.

<div><figure><img src="https://418253935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiJ406Lpi9EKoWDi7GFL7%2Fuploads%2Fz7wLtZft4dMjKqJPFlTm%2FFeatures_SSO_Google_Workspace_06.png?alt=media&#x26;token=3f526c21-de43-4dce-8d9d-1f1f104f7314" alt="Google Admin console with key steps to managing user access."><figcaption><p>Managing user access</p></figcaption></figure> <figure><img src="https://418253935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiJ406Lpi9EKoWDi7GFL7%2Fuploads%2Fbz2IcEyUEsQOFHLDbpiu%2FFeatures_SSO_Google_Workspace_07.png?alt=media&#x26;token=7db3ee48-57a8-40b2-ae99-4b7437659d59" alt="Google Admin console with key steps to managing user access."><figcaption><p>Managing user access</p></figcaption></figure></div>
