# Application Default Credentials (ADC) Guide
This guide explains how to use Application Default Credentials (ADC) with the Gemini Elixir client for Google Cloud authentication.
## Overview
Application Default Credentials (ADC) is a strategy used by Google Cloud client libraries to automatically find credentials based on the application environment. ADC provides a simple and consistent way to authenticate with Google Cloud APIs without hardcoding credentials in your application.
## How ADC Works
ADC searches for credentials in the following order:
1. **Environment Variable**: `GOOGLE_APPLICATION_CREDENTIALS` pointing to a service account JSON file
2. **User Credentials**: `~/.config/gcloud/application_default_credentials.json` (created via `gcloud auth application-default login`)
3. **GCP Metadata Server**: Automatic credentials for code running on Google Cloud Platform infrastructure
## Benefits
- **Environment-aware authentication**: Automatically uses the right credentials based on where your code runs
- **Simplified deployment**: No need to manage credentials differently for development vs. production
- **Secure**: Avoids hardcoding credentials in source code
- **Standardized**: Follows Google Cloud's recommended authentication practices
- **Automatic token refresh**: Handles token expiration and renewal automatically
- **Token caching**: Reduces API calls by caching access tokens with automatic expiration
## Setting Up ADC
### Option 1: Service Account Key File (Development & CI/CD)
Best for: Local development, testing, CI/CD pipelines
1. Create a service account in Google Cloud Console
2. Download the JSON key file
3. Set the environment variable:
```bash
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account-key.json"
```
**Example usage:**
```elixir
# ADC will automatically find and use the service account
{:ok, creds} = Gemini.Auth.ADC.load_credentials()
{:ok, token} = Gemini.Auth.ADC.get_access_token(creds)
# Use with Vertex AI
{:ok, project_id} = Gemini.Auth.ADC.get_project_id(creds)
config = %{
project_id: project_id,
location: "us-central1"
}
{:ok, response} = Gemini.generate("Hello!", auth: :vertex_ai)
```
**Security Note**: Never commit service account keys to version control. Use environment variables or secret management systems.
### Option 2: User Credentials (Development)
Best for: Local development with personal Google account
1. Install and configure the gcloud CLI
2. Run the ADC login command:
```bash
gcloud auth application-default login
```
This creates a credentials file at `~/.config/gcloud/application_default_credentials.json`.
**Example usage:**
```elixir
# ADC will automatically find and use your user credentials
{:ok, creds} = Gemini.Auth.ADC.load_credentials()
{:ok, token} = Gemini.Auth.ADC.get_access_token(creds)
# User credentials may include quota_project_id
case Gemini.Auth.ADC.get_project_id(creds) do
{:ok, project_id} ->
IO.puts("Using project: #{project_id}")
{:error, _} ->
# Set project ID explicitly if not in user credentials
config = %{project_id: "my-project-id", location: "us-central1"}
end
```
**Revoke access** when done:
```bash
gcloud auth application-default revoke
```
### Option 3: GCP Metadata Server (Production)
Best for: Production deployments on Google Cloud Platform
Works automatically on:
- **Compute Engine** VMs
- **Google Kubernetes Engine** (GKE) pods
- **Cloud Run** services
- **Cloud Functions**
- **App Engine** applications
**No setup required!** Just deploy your application to GCP.
**Example usage:**
```elixir
# On GCP, ADC automatically uses the metadata server
{:ok, creds} = Gemini.Auth.ADC.load_credentials()
# Automatically retrieves project ID from metadata
{:ok, project_id} = Gemini.Auth.ADC.get_project_id(creds)
# Token is fetched from metadata server
{:ok, token} = Gemini.Auth.ADC.get_access_token(creds)
# Ready to use with Vertex AI
{:ok, response} = Gemini.generate("Hello from Cloud Run!", auth: :vertex_ai)
```
**Service Account Permissions**: Ensure the Compute Engine default service account or your custom service account has the necessary permissions (e.g., `Vertex AI User` role).
## Using ADC in Your Application
### Basic Usage
```elixir
defmodule MyApp.GeminiClient do
alias Gemini.Auth.ADC
def call_gemini(prompt) do
with {:ok, creds} <- ADC.load_credentials(),
{:ok, token} <- ADC.get_access_token(creds),
{:ok, project_id} <- ADC.get_project_id(creds) do
# Use credentials with Vertex AI
Gemini.generate(
prompt,
auth: :vertex_ai,
project_id: project_id,
location: "us-central1"
)
else
{:error, reason} ->
{:error, "Authentication failed: #{reason}"}
end
end
end
```
### Integration with Vertex AI Strategy
The Vertex AI authentication strategy automatically falls back to ADC when no explicit credentials are provided:
```elixir
# If you provide project_id and location but no credentials,
# VertexStrategy will automatically try ADC
config = %{
project_id: "my-project",
location: "us-central1"
}
{:ok, response} = Gemini.generate("Hello!", auth: :vertex_ai)
```
### Checking ADC Availability
```elixir
if Gemini.Auth.ADC.available?() do
IO.puts("ADC credentials are available")
{:ok, creds} = Gemini.Auth.ADC.load_credentials()
# Use credentials...
else
IO.puts("No ADC credentials found")
# Fall back to explicit credentials or show error
end
```
### Getting Project Information
```elixir
{:ok, creds} = Gemini.Auth.ADC.load_credentials()
case Gemini.Auth.ADC.get_project_id(creds) do
{:ok, project_id} ->
IO.puts("Project ID: #{project_id}")
{:error, _} ->
# Some credential types don't include project ID
# Use environment variable or prompt user
project_id = System.get_env("VERTEX_PROJECT_ID") || "default-project"
end
```
## Token Caching
ADC automatically caches access tokens to reduce API calls and improve performance.
### How Caching Works
- **Automatic caching**: Tokens are cached after generation
- **Expiration handling**: Tokens are refreshed before they expire
- **Refresh buffer**: Tokens are refreshed 5 minutes before expiration (configurable)
- **Thread-safe**: Uses ETS for concurrent access
### Cache Behavior
```elixir
{:ok, creds} = Gemini.Auth.ADC.load_credentials()
# First call generates and caches token
{:ok, token1} = Gemini.Auth.ADC.get_access_token(creds)
# Second call uses cached token (no API call)
{:ok, token2} = Gemini.Auth.ADC.get_access_token(creds)
# Tokens are the same
token1 == token2 # => true
```
### Force Token Refresh
```elixir
{:ok, creds} = Gemini.Auth.ADC.load_credentials()
# Force a fresh token (bypasses cache)
{:ok, fresh_token} = Gemini.Auth.ADC.refresh_token(creds)
# Or use the force_refresh option
{:ok, fresh_token} = Gemini.Auth.ADC.get_access_token(creds, force_refresh: true)
```
### Custom Cache Keys
```elixir
# Use custom cache key for separate token pools
{:ok, creds} = Gemini.Auth.ADC.load_credentials()
{:ok, token} = Gemini.Auth.ADC.get_access_token(
creds,
cache_key: "my_app_vertex_ai"
)
```
## Troubleshooting
### No Credentials Found
**Error**: `"No credentials found via ADC"`
**Solutions**:
1. **Set GOOGLE_APPLICATION_CREDENTIALS**:
```bash
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/key.json"
```
2. **Run gcloud auth**:
```bash
gcloud auth application-default login
```
3. **Check if on GCP**:
```elixir
Gemini.Auth.MetadataServer.available?()
```
### Invalid Service Account File
**Error**: `"Failed to parse JSON"` or `"Invalid service account file format"`
**Solutions**:
- Verify the file is valid JSON
- Ensure it's a service account key (has `"type": "service_account"`)
- Download a fresh key from Google Cloud Console
- Check file permissions (must be readable)
### Token Generation Fails
**Error**: `"Failed to generate access token"`
**Solutions**:
1. **Service Account**: Verify the service account has necessary permissions
2. **User Credentials**: Re-authenticate with `gcloud auth application-default login`
3. **Metadata Server**: Check if running on GCP and service account is properly configured
### Project ID Not Available
**Error**: `"No project ID available in credentials"`
**Solutions**:
1. **Explicitly set project ID**:
```elixir
config = %{
project_id: "my-project-id",
location: "us-central1"
}
```
2. **Use environment variable**:
```bash
export VERTEX_PROJECT_ID="my-project-id"
```
3. **For user credentials**: Specify quota_project_id during gcloud auth
### Metadata Server Timeout
**Error**: `"Failed to contact metadata server"`
**Solutions**:
- Verify you're running on GCP infrastructure
- Check network connectivity
- Ensure metadata server is not blocked by firewall
- Verify service account is attached to the instance
## Best Practices
### 1. Environment-Specific Credentials
Use different credential sources for different environments:
```elixir
defmodule MyApp.Config do
def get_credentials do
case Mix.env() do
:prod ->
# Production: Use metadata server on GCP
if Gemini.Auth.MetadataServer.available?() do
Gemini.Auth.ADC.load_credentials()
else
{:error, "Production must run on GCP"}
end
:dev ->
# Development: Use service account or user credentials
Gemini.Auth.ADC.load_credentials()
:test ->
# Test: Use test credentials or mocks
{:ok, {:service_account, test_credentials()}}
end
end
end
```
### 2. Credential Validation at Startup
Validate credentials when your application starts:
```elixir
defmodule MyApp.Application do
use Application
def start(_type, _args) do
# Initialize token cache
Gemini.Auth.TokenCache.init()
# Validate ADC on startup
case Gemini.Auth.ADC.load_credentials() do
{:ok, creds} ->
Logger.info("ADC credentials loaded successfully")
case Gemini.Auth.ADC.get_access_token(creds) do
{:ok, _token} ->
Logger.info("Successfully authenticated with ADC")
{:error, reason} ->
Logger.warning("ADC token generation failed: #{reason}")
end
{:error, reason} ->
Logger.warning("ADC not available: #{reason}")
end
# Start your application...
children = [...]
Supervisor.start_link(children, strategy: :one_for_one)
end
end
```
### 3. Error Handling
Always handle credential errors gracefully:
```elixir
defmodule MyApp.GeminiClient do
def generate_with_retry(prompt, opts \\ []) do
max_retries = Keyword.get(opts, :max_retries, 3)
generate_with_retry_impl(prompt, opts, max_retries)
end
defp generate_with_retry_impl(prompt, opts, retries) when retries > 0 do
case Gemini.generate(prompt, opts) do
{:ok, response} ->
{:ok, response}
{:error, "Failed to get access token" <> _} when retries > 1 ->
# Token might be expired, force refresh
Logger.info("Refreshing ADC token and retrying...")
with {:ok, creds} <- Gemini.Auth.ADC.load_credentials(),
{:ok, _token} <- Gemini.Auth.ADC.refresh_token(creds) do
generate_with_retry_impl(prompt, opts, retries - 1)
else
{:error, reason} -> {:error, reason}
end
{:error, reason} ->
{:error, reason}
end
end
defp generate_with_retry_impl(_prompt, _opts, 0) do
{:error, "Max retries exceeded"}
end
end
```
### 4. Secure Credential Storage
Never commit credentials to version control:
```bash
# .gitignore
*.json
!config/*.json.example
.env
.env.local
```
Use environment variables or secret management:
```bash
# .env.example (commit this)
GOOGLE_APPLICATION_CREDENTIALS=/path/to/your/service-account-key.json
VERTEX_PROJECT_ID=your-project-id
VERTEX_LOCATION=us-central1
```
### 5. Monitoring and Logging
Monitor ADC credential usage:
```elixir
defmodule MyApp.Telemetry do
require Logger
def handle_event([:gemini, :auth, :adc, :token_cached], measurements, metadata, _config) do
Logger.debug("ADC token cached",
ttl: measurements.ttl,
credential_type: metadata.credential_type
)
end
def handle_event([:gemini, :auth, :adc, :token_refreshed], _measurements, metadata, _config) do
Logger.info("ADC token refreshed",
credential_type: metadata.credential_type,
source: metadata.source
)
end
end
```
## Examples
### Complete Application Example
```elixir
defmodule MyApp.VertexAI do
@moduledoc """
Vertex AI client using Application Default Credentials.
"""
alias Gemini.Auth.ADC
require Logger
@default_location "us-central1"
def generate(prompt, opts \\ []) do
with {:ok, creds} <- get_credentials(),
{:ok, token} <- ADC.get_access_token(creds),
{:ok, project_id} <- get_project_id(creds) do
location = Keyword.get(opts, :location, @default_location)
model = Keyword.get(opts, :model, "gemini-2.0-flash-lite")
Gemini.generate(
prompt,
auth: :vertex_ai,
project_id: project_id,
location: location,
model: model
)
else
{:error, reason} = error ->
Logger.error("Vertex AI generation failed: #{reason}")
error
end
end
defp get_credentials do
case ADC.load_credentials() do
{:ok, creds} = success ->
success
{:error, reason} ->
Logger.error("Failed to load ADC credentials: #{reason}")
{:error, "Authentication required. Please set up Application Default Credentials."}
end
end
defp get_project_id(creds) do
case ADC.get_project_id(creds) do
{:ok, project_id} ->
{:ok, project_id}
{:error, _} ->
# Fall back to environment variable
case System.get_env("VERTEX_PROJECT_ID") do
nil ->
{:error, "Project ID required. Set VERTEX_PROJECT_ID environment variable."}
project_id ->
{:ok, project_id}
end
end
end
end
# Usage
MyApp.VertexAI.generate("What is machine learning?")
```
### Testing with ADC
```elixir
defmodule MyApp.VertexAITest do
use ExUnit.Case, async: false
alias MyApp.VertexAI
@moduletag :live_api
setup do
# Ensure ADC is available for tests
case Gemini.Auth.ADC.available?() do
true ->
:ok
false ->
{:skip, "ADC credentials not available"}
end
end
test "generates content using ADC" do
assert {:ok, response} = VertexAI.generate("Hello!")
assert is_binary(response)
end
end
```
## Related Documentation
- [Authentication System](../../AUTHENTICATION_SYSTEM.md)
- [Rate Limiting Guide](rate_limiting.md)
- [Tuning Guide](tunings.md)
## Additional Resources
- [Google Cloud ADC Documentation](https://cloud.google.com/docs/authentication/application-default-credentials)
- [Service Account Best Practices](https://cloud.google.com/iam/docs/best-practices-service-accounts)
- [gcloud CLI Reference](https://cloud.google.com/sdk/gcloud/reference/auth/application-default)