How gog Manages Multiple Google Accounts with Isolated OAuth Clients and Automatic Selection
gog isolates multiple Google accounts by namespacing OAuth credentials and refresh tokens per client, then automatically selects the appropriate client based on email mappings, domain rules, or defaults defined in config.json.
The steipete/gogcli tool provides a robust solution for managing multiple Google accounts through isolated OAuth clients. Unlike standard Google CLI tools that rely on a single set of credentials, gog implements a multi-tenant architecture where each account can use distinct OAuth clients with separate permission scopes. This design ensures that work and personal credentials remain isolated while maintaining a seamless user experience through automatic client resolution.
Isolating OAuth Clients Per Account
gog achieves true isolation by separating both the client credentials and the resulting refresh tokens at the storage level.
Separate Credential Files
Each OAuth client maintains its own credentials file following the naming convention credentials-<name>.json, with the default client using credentials.json. In internal/googleauth/oauth_flow.go, the readClientCredentials function loads the specific file that matches the client name passed to the OAuth flow:
// Loads credentials-<client>.json or falls back to credentials.json
creds, err := readClientCredentials(clientName)
This ensures that client secrets never mix between different OAuth applications.
Namespaced Token Storage in OS Keyring
Refresh tokens are stored in the operating system keyring under keys formatted as token:<client>:<email>. The internal/secrets/store.go implementation isolates tokens per client, meaning a token obtained with client A cannot be accessed or reused by client B even for the same email address.
// Token storage key construction
key := fmt.Sprintf("token:%s:%s", clientName, email)
Automatic Client Resolution
gog eliminates the need to manually specify OAuth clients for every command through a hierarchical resolution system implemented in internal/config/clients.go.
Email-to-Client Mapping
The ResolveClientForAccount function checks the account_clients field in config.json to map specific email addresses to designated OAuth clients:
{
"account_clients": {
"[email protected]": "personal",
"[email protected]": "work"
}
}
When a command targets [email protected], gog automatically uses the work client credentials without explicit flags.
Domain-Based Client Assignment
For organizations with consistent domain naming, client_domains allows wildcard mapping based on email domains:
{
"client_domains": {
"@company.com": "corporate",
"@personal.net": "personal"
}
}
The resolution logic checks domain mappings after specific email mappings but before falling back to defaults.
Fallback Default Client
If no specific mapping exists, ResolveClientForAccount returns "default", ensuring that unconfigured accounts still function with the standard credentials.json file. This three-tier resolution—explicit override, email mapping, domain mapping, then default—provides flexibility without configuration overhead.
Practical Configuration and Usage
Setting Up Multiple OAuth Clients
-
Create separate credential files for each OAuth client:
cp ~/Downloads/client_secret_personal.json ~/.config/gogcli/credentials-personal.json cp ~/Downloads/client_secret_work.json ~/.config/gogcli/credentials-work.json -
Register the clients with
gog:gog auth credentials credentials-personal.json gog auth credentials credentials-work.json
Automatic Selection in Commands
Once configured, commands automatically select the correct client:
# Uses the "work" client based on config.json mapping
gog sheets list --account [email protected]
# Uses the "personal" client
gog slides list --account [email protected]
Internally, optionsForAccount in internal/googleapi/tasks.go (and similar files) handles this resolution:
client, err := config.ResolveClientForAccount(cfg, email, overrideClient)
tok, err := secrets.GetToken(client, email)
Setting Default Accounts Per Client
When running gog auth manage, the web interface allows setting a default account for each OAuth client. The server stores this in the keyring as default_account:<client> via SetDefaultAccount in internal/googleauth/accounts_server.go.
Summary
- Isolated Credentials: Each OAuth client uses separate
credentials-<name>.jsonfiles loaded byreadClientCredentialsininternal/googleauth/oauth_flow.go. - Namespaced Storage: Refresh tokens are stored as
token:<client>:<email>in the OS keyring viainternal/secrets/store.go, preventing cross-client token leakage. - Automatic Resolution:
ResolveClientForAccountininternal/config/clients.goimplements a hierarchy: CLI override → email mapping → domain mapping → default client. - Per-Client Defaults: The
accounts_server.goimplementation supports setting default accounts per OAuth client, stored separately in the keyring.
Frequently Asked Questions
How does gog securely store tokens for multiple Google accounts?
gog stores refresh tokens in the operating system keyring using the internal/secrets/store.go implementation. Each token is keyed as token:<client>:<email>, which isolates tokens not only by account but also by OAuth client. This ensures that even if two accounts share the same email address across different clients, their tokens remain separate and encrypted by the OS-native keyring service.
Can the same Google account be authorized with different OAuth clients in gog?
Yes. Because tokens are namespaced by client in the keyring (token:<client>:<email>), you can authorize [email protected] with both a "personal" client and a "work" client. Each authorization flow stores a distinct refresh token. When running commands, gog uses ResolveClientForAccount to determine which client—and therefore which token—to use based on your config.json mappings or domain rules.
What happens if no client is specified for an email address?
If no explicit client is provided via CLI flags or configuration, ResolveClientForAccount in internal/config/clients.go follows a fallback chain: it checks for an email-specific mapping in account_clients, then a domain mapping in client_domains, and finally defaults to the client named "default". This ensures that unconfigured accounts still function using the standard credentials.json file without requiring manual client selection for every command.
How do I set different default accounts for different OAuth clients?
Run gog auth manage to start the account management web server. The interface allows you to select a default account for each configured OAuth client. When you set a default, SetDefaultAccount in internal/googleauth/accounts_server.go stores the mapping in the keyring under default_account:<client>. Subsequent commands that omit the --account flag will automatically use this default for the resolved client, allowing you to maintain separate primary accounts for work and personal OAuth clients.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →