kiarina-lib-firebase-auth 1.36.0
Firebase authentication library for kiarina namespace
Requires Python: >=3.12
Keywords: authentication, client, firebase, pydantic, settings

# kiarina-lib-firebase-auth

Firebase authentication library with REST API integration and automatic token management.

## Purpose

`kiarina-lib-firebase-auth` provides a simple and secure way to manage Firebase authentication using REST APIs. This library enables custom token exchange and automatic ID token lifecycle management with configuration management using pydantic-settings-manager.

Key features:
- Custom token exchange for refresh/ID tokens via Firebase REST API
- Automatic ID token lifecycle management with `TokenManager`
- Token refresh 5 minutes before expiration
- Thread-safe token refresh with `asyncio.Lock`
- Secure API key management with SecretStr
- Multi-configuration support for different projects/environments
- Async-only API for modern Python applications
- Environment variable configuration

## Installation

```bash
pip install kiarina-lib-firebase-auth
```

## Quick Start

### Basic Usage

```python
from kiarina.lib.firebase.auth import (
    TokenManager,
    exchange_custom_token,
    settings_manager,
)

# Configure settings
settings_manager.user_config = {
    "default": {
        "project_id": "your-project-id",
        "api_key": "your-firebase-api-key",
    }
}

# Get settings
settings = settings_manager.get_settings()
api_key = settings.api_key.get_secret_value()

# Exchange custom token for ID and refresh tokens
custom_token = "your_custom_token_here"
token_data = await exchange_custom_token(custom_token, api_key)

# Create token manager for automatic token refresh
manager = TokenManager(
    api_key=api_key,
    token_data=token_data,
)

# Get ID token (automatically refreshes when needed)
id_token = await manager.get_id_token()
print(f"ID Token: {id_token}")

# Use the ID token for Firebase API calls
# The token will be automatically refreshed 5 minutes before expiration
```

### Manual Token Refresh

```python
from kiarina.lib.firebase.auth import refresh_id_token

# Manually refresh ID token using refresh token
token_data = await refresh_id_token(
    refresh_token="your_refresh_token",
    api_key="your_api_key",
)

print(f"New ID Token: {token_data.id_token}")
print(f"Expires at: {token_data.expires_at}")
```

## API Reference

### Settings

#### `FirebaseAuthSettings`

Configuration for Firebase authentication.

```python
from pydantic import SecretStr
from kiarina.lib.firebase.auth import FirebaseAuthSettings

settings = FirebaseAuthSettings(
    project_id="your-project-id",
    api_key=SecretStr("your-firebase-api-key"),
)
```

**Fields:**
- `project_id: str` - Firebase project ID
- `api_key: SecretStr` - Firebase Web API Key (obtain from Firebase Console)

### Functions

#### `exchange_custom_token(custom_token: str, api_key: str) -> TokenData`

Exchange a Firebase custom token for an ID token and refresh token.

**Parameters:**
- `custom_token: str` - Firebase custom token (JWT)
- `api_key: str` - Firebase Web API Key

**Returns:**
- `TokenData` - Contains `refresh_token`, `id_token`, and `expires_at`

**Raises:**
- `InvalidCustomTokenError` - If the custom token is invalid or expired
- `FirebaseAPIError` - If Firebase API returns an error

#### `refresh_id_token(refresh_token: str, api_key: str) -> TokenData`

Refresh ID token using refresh token.

**Parameters:**
- `refresh_token: str` - Firebase refresh token
- `api_key: str` - Firebase Web API Key

**Returns:**
- `TokenData` - Contains new `refresh_token`, `id_token`, and `expires_at`

**Raises:**
- `InvalidRefreshTokenError` - If refresh token is invalid or expired
- `FirebaseAPIError` - If Firebase API returns an error

### Classes

#### `TokenManager`

Service class for automatic ID token lifecycle management.

```python
from kiarina.lib.firebase.auth import TokenManager, TokenData

# Option 1: With token_data (recommended)
manager = TokenManager(
    api_key="your_api_key",
    token_data=token_data,
    refresh_buffer_seconds=300,  # Default: 5 minutes
)

# Option 2: With refresh_token only
manager = TokenManager(
    api_key="your_api_key",
    refresh_token="your_refresh_token",
    refresh_buffer_seconds=300,  # Default: 5 minutes
)

# Option 3: With token_data_cache for persistent storage
manager = TokenManager(
    api_key="your_api_key",
    token_data_cache=my_cache_implementation,
    refresh_buffer_seconds=300,  # Default: 5 minutes
)
```

**Constructor Parameters (all keyword-only):**
- `api_key: str` - **Required.** Firebase Web API Key
- `refresh_token: str | None` - Firebase refresh token (at least one of `refresh_token`, `token_data`, or `token_data_cache` is required)
- `token_data: TokenData | None` - Initial token data (at least one of `refresh_token`, `token_data`, or `token_data_cache` is required)
- `token_data_cache: TokenDataCache | None` - Cache implementation for persistent token storage (at least one of `refresh_token`, `token_data`, or `token_data_cache` is required)
- `refresh_buffer_seconds: int` - Refresh buffer time in seconds (default: 300)

**Methods:**
- `async get_id_token() -> str` - Get current ID token (auto-refreshes if needed)
- `async refresh() -> TokenData` - Manually refresh ID token

**Properties:**
- `id_token: str` - Current ID token
- `expires_at: datetime` - Token expiration time (UTC)

#### `TokenData`

Schema for Firebase authentication token data.

**Fields:**
- `refresh_token: str` - Refresh token for getting new ID tokens
- `id_token: str` - Firebase ID token (JWT)
- `expires_at: datetime` - ID token expiration time (UTC)

**Class Methods:**
- `from_api_response(id_token: str, refresh_token: str, expires_in: int, *, issued_at: datetime | None = None) -> TokenData` - Create TokenData from Firebase API response

#### `TokenDataCache`

Protocol for token data cache implementations.

Implementations should provide persistent storage for `TokenData`, allowing `TokenManager` to automatically save and restore token state.

```python
from kiarina.lib.firebase.auth import TokenDataCache, TokenData

class MyTokenCache(TokenDataCache):
    async def get(self) -> TokenData:
        # Load token data from persistent storage
        ...
    
    async def set(self, token_data: TokenData) -> None:
        # Save token data to persistent storage
        ...

# Use with TokenManager
manager = TokenManager(
    api_key="your_api_key",
    token_data_cache=MyTokenCache(),
)
```

**Methods:**
- `async get() -> TokenData` - Retrieve cached token data
- `async set(token_data: TokenData) -> None` - Store token data in cache

### Exceptions

#### `FirebaseAuthError`

Base exception for Firebase Auth errors.

#### `InvalidCustomTokenError`

Raised when custom token is invalid or expired.

#### `InvalidRefreshTokenError`

Raised when refresh token is invalid or expired.

#### `FirebaseAPIError`

Raised when Firebase API returns an error response.

**Attributes:**
- `status_code: int | None` - HTTP status code
- `error_code: str | None` - Firebase error code

## Configuration

### YAML Configuration

```yaml
kiarina.lib.firebase.auth:
  default:
    project_id: your-project-id
    api_key: your-firebase-api-key

  production:
    project_id: prod-project-id
    api_key: ${FIREBASE_API_KEY}  # From environment variable
```

### Environment Variables

Settings can be configured via environment variables with the `KIARINA_LIB_FIREBASE_AUTH_` prefix:

```bash
export KIARINA_LIB_FIREBASE_AUTH_PROJECT_ID=your-project-id
export KIARINA_LIB_FIREBASE_AUTH_API_KEY=your-firebase-api-key
```

### Multi-Configuration Support

```python
from kiarina.lib.firebase.auth import settings_manager

# Configure multiple environments
settings_manager.user_config = {
    "development": {
        "project_id": "dev-project",
        "api_key": "dev-api-key",
    },
    "production": {
        "project_id": "prod-project",
        "api_key": "prod-api-key",
    },
}

# Get settings for specific environment
dev_settings = settings_manager.get_settings("development")
prod_settings = settings_manager.get_settings("production")
```

## Testing

This package includes integration tests that require Firebase Admin SDK and Google Cloud authentication.

### Setup

1. Create a test settings file:

```yaml
# test_settings.yaml
kiarina.lib.google.auth:
  default:
    type: service_account
    project_id: your-project-id
    service_account_email: your-service-account@your-project.iam.gserviceaccount.com
    service_account_file: ~/.gcp/service-account/your-project/key.json

kiarina.lib.firebase.auth:
  default:
    project_id: your-project-id
    api_key: your-firebase-api-key
```

2. Set environment variable:

```bash
export KIARINA_LIB_FIREBASE_AUTH_TEST_SETTINGS_FILE=/path/to/test_settings.yaml
```

3. Run tests:

```bash
pytest tests/
```

## Dependencies

- `httpx>=0.28.1` - Async HTTP client for Firebase REST API
- `pydantic>=2.10.6` - Data validation and settings management
- `pydantic-settings>=2.10.1` - Settings management from environment
- `pydantic-settings-manager>=2.3.0` - Multi-configuration settings management

### Development Dependencies

- `firebase-admin>=6.6.0` - Firebase Admin SDK (for testing)
- `kiarina-lib-google-auth>=1.22.0` - Google Cloud authentication (for testing)

## License

This project is licensed under the MIT License.

## Related Projects

- [kiarina-lib-google-auth](https://github.com/kiarina/kiarina-python/tree/main/packages/kiarina-lib-google-auth) - Google Cloud authentication library
- [pydantic-settings-manager](https://github.com/kiarina/pydantic-settings-manager) - Multi-configuration settings management