Skip to main content

Overview

The CLI supports multiple authentication workflows designed for different environments:
  • Interactive OAuth2: For local desktop use with browser-based consent
  • Headless OAuth2: For CI/CD and servers using exported credentials
  • Service Accounts: For server-to-server authentication with optional domain-wide delegation
  • Pre-obtained Tokens: For environments where another tool (e.g., gcloud) manages tokens
All credentials except pre-obtained tokens are encrypted at rest using AES-256-GCM with keys stored in your OS keyring.

Authentication Methods

1. Interactive OAuth2 (Desktop)

Best for: Local development on machines with a browser.
gws auth setup   # One-time: creates GCP project, enables APIs, logs you in
gws auth login   # Subsequent logins
gws auth setup requires the gcloud CLI to be installed and authenticated. It automates:
  • Creating a Google Cloud project
  • Enabling required APIs
  • Creating an OAuth client
  • Completing the login flow
Manual Setup (if you prefer explicit control):
  1. Create an OAuth client in Google Cloud Console
  2. Download client_secret.json and save to ~/.config/gws/client_secret.json
  3. Run gws auth login
The CLI starts a local loopback server at http://127.0.0.1:8080 to receive the OAuth callback.

2. Headless OAuth2 (CI/CD)

Best for: Environments without a browser (servers, GitHub Actions, Docker containers). Step 1: Export credentials from a machine with a browser:
gws auth export --unmasked > credentials.json
The exported file contains your refresh token in plaintext. Treat it like a password:
  • Never commit to version control
  • Use secrets management (GitHub Secrets, Vault, etc.)
  • Restrict file permissions: chmod 600 credentials.json
Step 2: Use the exported credentials in your headless environment:
export GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE=/path/to/credentials.json
gws drive files list --params '{"pageSize": 5}'
Or in a Docker container:
FROM node:20-slim
RUN npm install -g @googleworkspace/cli
COPY credentials.json /secrets/
ENV GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE=/secrets/credentials.json
CMD ["gws", "drive", "files", "list"]

3. Service Account

Best for: Server-to-server authentication, automated workflows, production systems. Step 1: Create a Service Account in Google Cloud Console. Step 2: Download the JSON key file. Step 3: Set the environment variable:
export GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE=/path/to/service-account.json
gws drive files list

Domain-Wide Delegation

For accessing user data with a Service Account, enable Domain-Wide Delegation:
  1. In Google Workspace Admin Console, go to Security > API Controls > Domain-wide Delegation
  2. Add your Service Account’s Client ID with the required scopes
  3. Set the impersonated user email:
export GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE=/path/to/service-account.json
export GOOGLE_WORKSPACE_CLI_IMPERSONATED_USER=admin@example.com
gws drive files list  # Lists files for admin@example.com
Implementation in src/auth.rs:86-98:
Credential::ServiceAccount(key) => {
    let token_cache = config_dir.join("service_account_token_cache.json");
    let mut builder =
        yup_oauth2::ServiceAccountAuthenticator::builder(key).with_storage(Box::new(
            crate::token_storage::EncryptedTokenStorage::new(token_cache),
        ));

    // Check for impersonation
    if let Some(user) = impersonated_user {
        if !user.trim().is_empty() {
            builder = builder.subject(user.to_string());
        }
    }

    let auth = builder.build().await
        .context("Failed to build service account authenticator")?;

    let token = auth.token(scopes).await.context("Failed to get token")?;
    Ok(token
        .token()
        .ok_or_else(|| anyhow::anyhow!("Token response contained no access token"))?
        .to_string())
}

4. Pre-obtained Access Token

Best for: Environments where another tool already manages authentication.
export GOOGLE_WORKSPACE_CLI_TOKEN=$(gcloud auth print-access-token)
gws gmail users messages list --params '{"userId": "me"}'
Access tokens expire after 1 hour. For long-running processes, use OAuth2 or Service Account credentials (which support automatic refresh).

Credential Precedence

When multiple credential sources are available, the CLI uses this priority order:
PrioritySourceSet viaSupports Refresh
1Access tokenGOOGLE_WORKSPACE_CLI_TOKENNo
2Credentials fileGOOGLE_WORKSPACE_CLI_CREDENTIALS_FILEYes
3Encrypted credentials (OS keyring)gws auth loginYes
4Plaintext credentials~/.config/gws/credentials.jsonYes
Implementation in src/auth.rs:41-61:
pub async fn get_token(scopes: &[&str]) -> anyhow::Result<String> {
    // 0. Direct token from env var (highest priority, bypasses all credential loading)
    if let Ok(token) = std::env::var("GOOGLE_WORKSPACE_CLI_TOKEN") {
        if !token.is_empty() {
            return Ok(token);
        }
    }

    let creds_file = std::env::var("GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE").ok();
    let impersonated_user = std::env::var("GOOGLE_WORKSPACE_CLI_IMPERSONATED_USER").ok();
    let config_dir = dirs::config_dir()
        .unwrap_or_else(|| PathBuf::from("."))
        .join("gws");

    let enc_path = credential_store::encrypted_credentials_path();
    let default_path = config_dir.join("credentials.json");

    let creds = load_credentials_inner(creds_file.as_deref(), &enc_path, &default_path).await?;

    get_token_inner(scopes, creds, &config_dir, impersonated_user.as_deref()).await
}

Security Features

AES-256-GCM Encryption

All locally stored credentials (from gws auth login) are encrypted at rest:
  • Algorithm: AES-256-GCM (authenticated encryption)
  • Key Storage: OS keyring (Keychain on macOS, Windows Credential Manager, Secret Service on Linux)
  • Encrypted File: ~/.config/gws/credentials.enc
Implementation uses the keyring crate:
// Store encryption key in OS keyring
let entry = keyring::Entry::new("gws-cli", "encryption-key")?;
let key = generate_or_retrieve_key(&entry)?;

// Encrypt credentials with AES-256-GCM
let cipher = Aes256Gcm::new(GenericArray::from_slice(&key));
let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
let ciphertext = cipher.encrypt(&nonce, plaintext.as_bytes())
    .expect("encryption failure");

Token Caching

Access tokens are cached separately from credentials to minimize refresh requests:
  • OAuth2 tokens: ~/.config/gws/token_cache.json (encrypted)
  • Service Account tokens: ~/.config/gws/service_account_token_cache.json (encrypted)
Tokens are refreshed automatically when they expire (typically after 1 hour).

Scope Minimization

The CLI only requests scopes required by the specific API method being invoked. Scopes are read from the Discovery Document:
// From src/discovery.rs:84-86
pub struct RestMethod {
    // ...
    #[serde(default)]
    pub scopes: Vec<String>,
}
Example from Drive API:
{
  "methods": {
    "list": {
      "scopes": [
        "https://www.googleapis.com/auth/drive",
        "https://www.googleapis.com/auth/drive.readonly"
      ]
    }
  }
}

Environment Variables

All environment variables can also be defined in a .env file in your working directory:
# .env
GOOGLE_WORKSPACE_CLI_TOKEN=ya29.a0AfH6SMB...
GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE=/secrets/credentials.json
GOOGLE_WORKSPACE_CLI_IMPERSONATED_USER=admin@example.com
The CLI loads .env automatically via the dotenvy crate.

Troubleshooting

”No credentials found”

You haven’t authenticated yet. Run:
gws auth setup   # or gws auth login

“Token response contained no access token”

Your credentials file is malformed or missing required fields. Expected format:
{
  "client_id": "...",
  "client_secret": "...",
  "refresh_token": "...",
  "type": "authorized_user"
}

“Failed to build service account authenticator”

Your Service Account JSON key is invalid or missing required fields. Download a fresh key from Google Cloud Console.

Testing Credential Precedence

To verify which credential source is being used, set the token env var to a dummy value:
export GOOGLE_WORKSPACE_CLI_TOKEN="test-token"
gws drive files list  # Will fail with 401, but proves env var takes precedence

Dynamic Discovery

How the CLI builds commands at runtime

Auth Commands

Detailed reference for all auth commands