Resolving Google Chat User IDs to Emails: The Least Privilege Way

Published: (December 23, 2025 at 01:43 PM EST)
4 min read
Source: Dev.to

Source: Dev.to

Google Workspace Developers profile image
Justin Poehnelt

If you’ve built a Google Chat bot, you’ve likely hit this wall: the Chat API sends you a membership event with a user ID (e.g., users/1154…), but omits the email field entirely. Your business logic usually needs that email address.

  • You might try the People API, but it returns empty fields because the service account has no contacts.
  • The Admin SDK works, but a default service‑account call returns 403 Forbidden because it lacks admin privileges.
  • Domain‑Wide Delegation (DwD) would let the bot impersonate an admin, but granting a bot permission to impersonate any user feels like using a sledgehammer to crack a nut.

There is a better way. By assigning a custom read‑only admin role directly to the service account, you can resolve emails securely without granting blanket domain access.

The Strategy

Instead of using DwD to let the service account impersonate an admin, we make the service account an admin itself—strictly a read‑only admin with a custom role.

Read Users Custom Role Assigned to Service Account
Read Users custom role assigned to the service account.

1. Create a Custom Role

  1. Open the Google Admin ConsoleRolesCreate a custom role.
  2. Give it a name (e.g., Read Users).
  3. Add one permission: Admin API Privileges > Users > Read.
  4. Save the role.
    Learn more about custom roles.

2. Assign the Role

Assign the newly created custom role directly to the service‑account’s email address (e.g., my-bot@my-project.iam.gserviceaccount.com).

3. Local Development

Use IAM Service Account Impersonation (acting as the service account) to run scripts locally, instead of Domain‑Wide Delegation (the service account acting as a user). This keeps your local environment key‑free.

The Solution

In production on Google Cloud, your code will typically use Application Default Credentials (ADC), which authenticate as the service account. Because the service account itself now holds the admin privileges, no extra impersonation logic is required—the API calls just work.

For local testing, we avoid long‑lived JSON keys by using Service Account Impersonation to temporarily act as the bot.

Bash script – local testing flow

#!/bin/bash
# -------------------------------------------------
# Prerequisites
# -------------------------------------------------
# 1. In Google Cloud IAM, grant your user the role
#    "Service Account Token Creator" on the service account.
# 2. In Google Workspace, create a custom admin role with
#    "Admin API Privileges > Users > Read" and assign it
#    to the service‑account email.
# -------------------------------------------------
# Configuration
# -------------------------------------------------
# Replace with the ID from the Chat API
TARGET_USER_ID="115429828439139643037"
SERVICE_ACCOUNT="your-bot@your-project.iam.gserviceaccount.com"

echo "Generating impersonated access token for $SERVICE_ACCOUNT..."

# 1️⃣ Generate an access token for the service account.
#    We request the admin.directory.user.readonly scope.
ACCESS_TOKEN=$(gcloud auth print-access-token \
    --impersonate-service-account="$SERVICE_ACCOUNT" \
    --scopes="https://www.googleapis.com/auth/admin.directory.user.readonly")

if [[ -z "$ACCESS_TOKEN" ]]; then
    echo "Error: Failed to generate impersonated token."
    exit 1
fi

# 2️⃣ Query Admin SDK
echo "Querying Admin SDK for User ID: $TARGET_USER_ID..."
RESPONSE=$(curl -fs -X GET \
    -H "Authorization: Bearer $ACCESS_TOKEN" \
    -H "Content-Type: application/json" \
    "https://admin.googleapis.com/admin/directory/v1/users/${TARGET_USER_ID}?projection=basic")

# 3️⃣ Parse the output
EMAIL=$(echo "$RESPONSE" | jq -r '.primaryEmail')
echo "Resolved email: $EMAIL"

Note: The warning --scopes flag may not work as expected and will be ignored for account type impersonated_account is a known quirk in the gcloud CLI when using impersonation. As shown in the token payload below, the requested scope is still present and the API call succeeds.

Example access‑token payload

{
  "issued_to": "108111659397065155772",
  "audience": "108111659397065155772",
  "user_id": "108111659397065155772",
  "scope": "https://www.googleapis.com/auth/userinfo.email openid https://www.googleapis.com/auth/admin.directory.user.readonly",
  "expires_in": 3599,
  "email": "admin-read-test@list-user-emails-test.iam.gserviceaccount.com",
  "verified_email": true,
  "access_type": "online"
}

Query result

------------------------------------
Success! Resolved to: justin@example.com
------------------------------------

Beyond Email Resolution

Because the Service Account acts as itself, it is not restricted to just resolving IDs. With the Users > Read permission, it can also:

  • Use the Admin SDK to Search Users
  • List organizational units

All without being a Super Admin or requiring Domain‑Wide Delegation.

Why this is better

  • No Keys – No long‑lived JSON key files are downloaded to your laptop.
  • Audit Trails – The Admin Audit log shows the Service Account performing the read, rather than an impersonated Super Admin.
  • Least Privilege – The bot can only read users. It cannot delete accounts, reset passwords, or read Gmail—common risks associated with broad Domain‑Wide Delegation scopes.

This approach keeps your security team happy and your bots functional.


Resolving Google Chat User IDs to Emails: The Least Privilege Way © 2025 by Justin Poehnelt is licensed under CC BY‑SA 4.0.

Back to Blog

Related posts

Read more »

How to create telegram bots in Ruby

!Cover image for How to create telegram bots in Rubyhttps://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fd...