# aisuite Provider Authentication Pattern: API Keys, IAM Roles, and Environment Variables Explained

> Learn the aisuite provider authentication pattern. Understand how to use API keys, IAM roles, and environment variables for secure access to cloud services.

- Repository: [Andrew Ng/aisuite](https://github.com/andrewyng/aisuite)
- Tags: architecture
- Published: 2026-08-03

---

**aisuite implements provider-specific authentication through a base `Provider` class with a standardized `**config` constructor that falls back to environment variables, enabling both explicit API keys and implicit IAM role-based access.**

aisuite unifies dozens of LLM and ASR services behind a single interface. The library's authentication architecture lets developers pass credentials directly or rely on environment-based discovery, depending on each provider's security model. This article examines the implementation pattern for **provider-specific authentication** in aisuite, from the abstract base class through concrete provider implementations.

## The Three-Layer Authentication Architecture

aisuite separates authentication concerns across three components:

- **`Provider` abstract base class** — defines the contract all providers must implement
- **`ProviderFactory`** — dynamically loads provider modules using naming conventions
- **Concrete provider classes** — implement credential retrieval and client initialization

### Base Provider Contract

The abstract base class in [`aisuite/provider.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/provider.py) establishes the interface that every provider must satisfy.

```python

# aisuite/provider.py#L24-L30

class Provider(ABC):
    @abstractmethod
    def chat_completions_create(self, model, messages, **kwargs):
        """Create a chat completion for the given model."""
        pass

```

Concrete providers inherit from `Provider` and only implement the chat and audio methods. Authentication logic lives entirely in the subclass constructor.

## ProviderFactory: Dynamic Loading with Config Injection

The `ProviderFactory.create_provider()` method handles runtime discovery and instantiation. It imports modules following the pattern `<provider_key>_provider` and instantiates `<ProviderKey>Provider`, passing the complete configuration dictionary.

```python

# aisuite/provider.py#L92-L106

@staticmethod
def create_provider(provider_key: str, config: dict):
    module_name = f"aisuite.providers.{provider_key}_provider"
    module = importlib.import_module(module_name)
    
    class_name = f"{provider_key.title().replace('_', '')}Provider"
    provider_class = getattr(module, class_name)
    
    return provider_class(**config)

```

This design means **all authentication parameters travel through the `**config` dictionary**, with each provider deciding how to interpret those values.

## The Six-Step Authentication Pattern

Every concrete provider in aisuite follows an identical credential handling sequence:

1. **Accept `**config`** — constructor signature is always `def __init__(self, **config)`
2. **Prefer explicit config** — check `config.get("api_key")`, `config.get("base_url")`, etc.
3. **Fall back to environment** — use `os.getenv("PROVIDER_API_KEY")` when config values are absent
4. **Validate presence** — raise `ValueError` with a clear message if required credentials are missing
5. **Initialize vendor client** — pass normalized config to the underlying SDK
6. **Store client and converter** — keep references for request/response normalization

## API Key-Based Authentication Examples

### OpenAI Provider

OpenAI requires an API key that can come from either explicit config or the `OPENAI_API_KEY` environment variable.

```python

# aisuite/providers/openai_provider.py#L15-L23

class OpenAiProvider(Provider):
    def __init__(self, **config):
        self.config = config
        # config dict is passed directly to openai.OpenAI()

        # which reads api_key from config or OPENAI_API_KEY env var

        self.client = openai.OpenAI(**config)

```

The `openai` Python SDK itself handles the environment fallback, so aisuite delegates this responsibility.

### Azure Provider

Azure OpenAI requires both an API key and base URL, with explicit validation.

```python

# aisuite/providers/azure_provider.py#L81-L86

def __init__(self, **config):
    self.api_key = config.get("api_key", os.getenv("AZURE_API_KEY"))
    self.base_url = config.get("base_url", os.getenv("AZURE_BASE_URL"))
    
    if not self.api_key or not self.base_url:
        raise ValueError("Azure API key and base URL are required")

```

### Groq Provider

Groq demonstrates the full pattern with explicit environment fallback.

```python

# aisuite/providers/groq_provider.py#L31-L40

class GroqProvider(Provider):
    def __init__(self, **config):
        self.api_key = config.get("api_key", os.getenv("GROQ_API_KEY"))
        
        if not self.api_key:
            raise ValueError("GROQ_API_KEY is not set")
        
        self.client = groq.Groq(api_key=self.api_key)

```

## IAM Role Authentication: AWS Bedrock Pattern

AWS Bedrock represents the alternative model where **no API key exists**. Instead, authentication relies on AWS's standard credential provider chain.

```python

# aisuite/providers/aws_provider.py#L17-L23

class AwsProvider(Provider):
    def __init__(self, **config):
        self.config = BedrockConfig(**config)  # reads AWS_REGION env var, defaults to us-west-2

        self.client = self.config.create_client()  # boto3 resolves IAM role, instance profile, etc.

```

The `BedrockConfig` class extracts the region from `config` or `AWS_REGION`, then `boto3.client("bedrock-runtime")` automatically discovers credentials through:

- Environment variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`)
- IAM roles attached to EC2 instances or ECS tasks
- AWS SSO or IAM Identity Center profiles
- Local AWS CLI configuration

This means **zero secrets appear in code or environment variables** when running on properly configured AWS infrastructure.

## Practical Usage Examples

### Explicit API Key Configuration

```python
from aisuite.provider import ProviderFactory

config = {
    "api_key": "sk-my-openai-key",  # omit if OPENAI_API_KEY is set

    "base_url": "https://api.openai.com/v1"
}
openai = ProviderFactory.create_provider("openai", config)

response = openai.chat_completions_create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Hello"}]
)
print(response)

```

### IAM Role-Based Access (No Secrets Required)

```python

# Running on AWS infrastructure with IAM role attached

aws = ProviderFactory.create_provider("aws", {})  # empty config

resp = aws.chat_completions_create(
    model="anthropic.claude-v2",
    messages=[{"role": "user", "content": "Summarize this text"}],
    maxTokens=512,
    temperature=0.7
)
print(resp)

```

### Direct Provider Instantiation with Environment Fallback

```python
import os
os.environ["GROQ_API_KEY"] = "gsk-my-groq-key"

from aisuite.providers.groq_provider import GroqProvider

groq = GroqProvider()  # api_key pulled from GROQ_API_KEY automatically

stream = groq.chat_completions_create(
    model="llama3-groq-70b-8192-tool-use-preview",
    messages=[{"role": "user", "content": "What is the capital of France?"}]
)
print(stream)

```

## Key Implementation Files

| File | Purpose |
|------|---------|
| [`aisuite/provider.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/provider.py) | Abstract `Provider` class and `ProviderFactory` |
| [`aisuite/providers/openai_provider.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/providers/openai_provider.py) | OpenAI API key handling |
| [`aisuite/providers/azure_provider.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/providers/azure_provider.py) | Azure OpenAI with dual credential requirement |
| [`aisuite/providers/groq_provider.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/providers/groq_provider.py) | Groq with environment fallback |
| [`aisuite/providers/aws_provider.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/providers/aws_provider.py) | AWS Bedrock IAM role authentication |
| [`aisuite/providers/message_converter.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/providers/message_converter.py) | Request/response normalization |

## Summary

- **aisuite authentication is provider-agnostic at the interface level** — the `ProviderFactory` and base class know nothing about credential mechanisms
- **Concrete providers implement the six-step pattern**: accept `**config`, prefer explicit values, fall back to environment, validate, initialize client, store references
- **API key providers** (OpenAI, Groq, Azure) require explicit secrets that can pass through config or environment variables
- **IAM role providers** (AWS Bedrock) delegate credential discovery to the underlying SDK, enabling secret-free operation on cloud infrastructure
- **Adding new providers requires only**: creating `<name>_provider.py`, inheriting from `Provider`, and following the constructor convention

## Frequently Asked Questions

### How does aisuite handle missing API keys?

Each concrete provider validates required credentials in `__init__` and raises a descriptive `ValueError` if credentials are absent. For example, the Azure provider checks both `api_key` and `base_url`, while Groq validates only `api_key`.

### Can I use AWS Bedrock without any environment variables?

Only `AWS_REGION` is recommended; it defaults to `us-west-2` if unset. No API keys, access keys, or secrets are required when running on AWS infrastructure with an attached IAM role. The `boto3` library resolves credentials through the standard AWS credential provider chain.

### Why does aisuite use `**config` instead of explicit parameters?

The `**config` pattern enables forward compatibility and provider-specific extensions without modifying the base interface. Each provider extracts the keys it needs while ignoring others, allowing the same configuration dictionary to contain parameters for multiple providers simultaneously.