authmanagersdk 1.0.1
Python SDK for the Auth Manager API.
License: Apache-2.0
Requires Python: >=3.10
Keywords: api, auth, authentication, manager, sdk

# Auth Manager SDK
[authmanager]: https://github.com/openbraininstitute/authmanager
[license_badge]: https://img.shields.io/pypi/l/authmanagersdk
[license_target]: https://github.com/openbraininstitute/authmanagersdk/blob/main/LICENSE.txt
[pypi_badge]: https://github.com/openbraininstitute/authmanagersdk/actions/workflows/sdist.yml/badge.svg
[pypi_target]: https://pypi.org/project/authmanagersdk/
[![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)

A Python SDK for the Auth Manager API providing OAuth2 token management, validation, and offline token handling.

## Features

- **Token Management** - Access token retrieval and validation
- **Refresh Tokens** - Store and manage refresh tokens
- **Offline Tokens** - OAuth consent flow for offline access
- **Token Validation** - Verify token authenticity and validity

## Installation

```bash
pip install authmanagersdk
```

Or using `uv`:

```bash
uv add authmanagersdk
```

## Quick Start

```python
import asyncio
from authmanagersdk import AuthManager

async def main():
    # initialize client
    async with AuthManager(
        base_url="https://api.example.com",
        token="your-bearer-token"  # optional
    ) as client:
        # validate token
        result = await client.validation.verify()
        print(f"Token valid: {result.valid}")

if __name__ == "__main__":
    asyncio.run(main())
```

## API Reference

### Client Initialization

```python
from authmanagersdk import AuthManager

# basic initialization
client = AuthManager(base_url="https://api.example.com")

# with authentication token
client = AuthManager(
    base_url="https://api.example.com",
    token="your-bearer-token",
    timeout=30.0  # optional, default: 10.0
)

# use as context manager
async with AuthManager(base_url="...") as client:
    ...
```

### Access Tokens

Retrieve access tokens by ID.

```python
from uuid import UUID

persistent_token_id = UUID("123e4567-e89b-12d3-a456-426614174000")
result = await client.access.retrieve(id=persistent_token_id)

print(f"Access Token: {result.access_token}")
print(f"Expires In: {result.expires_in} seconds")
```

**Response Model**: `AccessTokenResult`
- `access_token: str` - The access token string
- `expires_in: int` - Token expiration time in seconds

### Offline Tokens

Handle OAuth consent flow for offline access tokens.

#### Initiate Consent

```python
# get consent url
consent = await client.offline.request_consent(
    client_id="your-client-id",
    user_id="user-123"
)

print(f"Redirect user to: {consent.consent_url}")
```

#### Handle Callback

```python
# after user consents and is redirected back
result = await client.offline.callback(
    code="auth-code-from-redirect",
    state="state-from-initiate"
)

print(f"Persistent Token ID: {result.persistent_token_id}")
```

#### Handle Callback Errors

```python
# if user denies consent
result = await client.offline.callback(
    code="",
    state="state-from-initiate",
    error="access_denied",
    error_description="User denied consent"
)
```

#### Revoke Offline Token

```python
from uuid import UUID

persistent_token_id = UUID("123e4567-e89b-12d3-a456-426614174000")
result = await client.offline.revoke(id=persistent_token_id)

print(f"Revoked: {result.revoked}")
```

**Models**:
- `OfflineConsentResult` - Consent initiation response
- `OfflineTokenResult` - Offline token callback response
- `OfflineTokenRevocationResult` - Revocation response

### Refresh Tokens

Store refresh tokens for later use.

```python
# store refresh token
result = await client.refresh.store(
    refresh_token="your-refresh-token-string"
)

print(f"Stored with ID: {result.id}")
```

**Response Model**: `RefreshTokenIdResult`
- `id: UUID` - The stored refresh token ID

### Token Validation

Verify token authenticity and validity.

```python
# requires authenticated client
async with AuthManager(
    base_url="https://api.example.com",
    token="token-to-validate"
) as client:
    result = await client.validation.verify()
    print(f"Valid: {result.valid}")
```

**Response Model**: `TokenValidationResult`
- `valid: bool` - Whether token is valid

## Authentication

The SDK supports Bearer token authentication:

```python
# set token during initialization
client = AuthManager(
    base_url="https://api.example.com",
    token="your-bearer-token"
)

# or set/update token later
client.set_token("new-bearer-token")

```

## Configuration

### Environment Variables

You can configure the API URL via environment variable:

```bash
export AUTH_MANAGER_API_URL=https://api.example.com
```

Then in your code:

```python
import os
from authmanagersdk import AuthManager

api_url = os.getenv("AUTH_MANAGER_API_URL", "http://localhost:8000")
client = AuthManager(base_url=api_url)
```

### Timeouts

Configure request timeout:

```python
client = AuthManager(
    base_url="https://api.example.com",
    timeout=30.0
)
```

## Error Handling

The SDK raises specific exceptions for different error scenarios:

```python
from authmanagersdk.schemas.exceptions import (
    AuthManagerApiError,
    AuthManagerConnectionError
)

try:
    async with AuthManager(base_url="...") as client:
        result = await client.validation.verify()
except AuthManagerConnectionError as e:
    print(f"Connection failed: {e}")
except AuthManagerApiError as e:
    print(f"API error: {e}")
    print(f"Status code: {e.status_code}")
    print(f"Response: {e.response}")
```

**Exception Types**:
- `AuthManagerConnectionError` - Network/connection issues
- `AuthManagerApiError` - API errors (4xx, 5xx responses)

## Type Safety

All models are auto-generated from the OpenAPI spec:

```python
from authmanagersdk.schemas.models import (
    AccessTokenResult,
    OfflineTokenResult,
    RefreshTokenIdResult,
    TokenValidationResult,
)
```

## Development

This project uses [`uv`](https://github.com/astral-sh/uv) for dependency management.

### Setup

```bash
# Install dependencies
uv sync

# Run tests
uv run pytest

# Run linter
uv run ruff check

# Format code
uv run ruff format

# Type checking
uv run ty check
```

### Generate Models

Models are auto-generated from the backend OpenAPI spec:

```bash
# start the backend first
cd ../auth-manager && make dev-local

# generate models (in another terminal)
cd sdk && make generate
```

### Running Tests

```bash
# All tests
make test

```

## Versioning

### Version Format

Tags **MUST** start with `sdk/` prefix to be recognized:
- `sdk/0.1.0` → version `0.1.0`
- `sdk/1.2.3` → version `1.2.3`
- `sdk/2.0.0-beta.1` → version `2.0.0b1`

**Note**: Tags without `sdk/` prefix (e.g., `v1.0.0`) will be ignored by the SDK build process.

### Release Process

#### 1. Make Changes
```bash
# Make your changes
git add .
git commit -m "Add new feature"
```

#### 2. Create Version Tag
```bash
# For new release
git tag sdk/0.2.0

# Push tag
git push origin sdk/0.2.0
```



## Contributing

We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.

## License

Copyright (c) 2025 Open Brain Institute

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.