Service Integration

ActingWeb provides a unified, modern interface for integrating with third-party OAuth2-protected services like Dropbox, Gmail, GitHub, and Box. This system replaces the legacy OAuth class with a clean, developer-friendly API built on top of the new OAuth2 foundation.

Overview

The service integration system provides:

  • Unified Configuration: Register services using fluent API methods or templates

  • Automatic Token Management: Handles access token refresh and storage transparently

  • Clean Developer Interface: Similar to other ActingWeb functionality

  • Per-Actor Authentication: Each actor can authenticate to services independently

  • Trust Relationship Storage: Service authentication details stored in trust relationships

Quick Start

  1. Configure Services in your ActingWeb app:

from actingweb.interface import ActingWebApp

app = (
    ActingWebApp(
        aw_type="urn:actingweb:example.com:myapp",
        database="dynamodb",
        fqdn="myapp.example.com"
    )
    .with_oauth(...)  # Configure user authentication
    .add_dropbox("dropbox_client_id", "dropbox_client_secret")
    .add_gmail("gmail_client_id", "gmail_client_secret", readonly=True)
    .add_github("github_client_id", "github_client_secret")
)
  1. Use Services in your application code:

@app.action_hook("sync_files")
def sync_files(actor: ActorInterface, action_name: str, data: Dict[str, Any]) -> Any:
    # Get authenticated Dropbox client
    dropbox = actor.services.get("dropbox")

    if not dropbox.is_authenticated():
        # Return authorization URL for user to authenticate
        return {
            "error": "Dropbox authentication required",
            "auth_url": dropbox.get_authorization_url()
        }

    # Make API calls to Dropbox
    files = dropbox.get("/2/files/list_folder", {"path": "/Documents"})
    return {"files": files}

Configuration

Service Registration

Use fluent API methods to register services:

Pre-configured Templates:

app.add_dropbox(client_id, client_secret)
app.add_gmail(client_id, client_secret, readonly=True)  # readonly=False for write access
app.add_github(client_id, client_secret)
app.add_box(client_id, client_secret)

Custom Services:

app.add_service(
    name="custom_service",
    client_id="your_client_id",
    client_secret="your_client_secret",
    scopes=["read", "write"],
    auth_uri="https://service.com/oauth/authorize",
    token_uri="https://service.com/oauth/token",
    userinfo_uri="https://service.com/oauth/userinfo",  # optional
    revocation_uri="https://service.com/oauth/revoke",  # optional
    base_api_url="https://api.service.com/v1",
    access_type="offline",  # extra OAuth parameters
    prompt="consent"
)

Advanced Configuration:

# Get service registry for advanced configuration
registry = app.get_service_registry()

# Register custom service configuration
from actingweb.interface.services import ServiceConfig

custom_config = ServiceConfig(
    name="advanced_service",
    client_id="client_id",
    client_secret="client_secret",
    scopes=["custom.read", "custom.write"],
    auth_uri="https://auth.service.com/oauth2/auth",
    token_uri="https://auth.service.com/oauth2/token",
    base_api_url="https://api.service.com/v2",
    extra_params={"access_type": "offline", "approval_prompt": "force"}
)

registry.register_service(custom_config)

Usage

Accessing Services

Each actor has a services property that provides access to authenticated service clients:

# Get service client
service_client = actor.services.get("dropbox")

# Check authentication status
if service_client.is_authenticated():
    # Make API calls
    pass
else:
    # Redirect user to authenticate
    auth_url = service_client.get_authorization_url()

Authentication Flow

1. Check Authentication:

dropbox = actor.services.get("dropbox")
if not dropbox.is_authenticated():
    return {"auth_url": dropbox.get_authorization_url()}

2. User Authorization:

The user visits the authorization URL and grants permissions. The service redirects back to: https://yourdomain.com/{actor_id}/services/{service_name}/callback

3. Automatic Token Exchange:

ActingWeb automatically handles the OAuth2 callback, exchanges the authorization code for tokens, and stores them securely.

Making API Calls

Service clients provide convenient HTTP methods:

# GET request
files = dropbox.get("/2/files/list_folder", {"path": "/Documents"})

# POST request
result = dropbox.post("/2/files/create_folder_v2", {
    "path": "/NewFolder",
    "autorename": False
})

# PUT request
updated = service.put("/api/resource/123", {"name": "Updated Name"})

# DELETE request
deleted = service.delete("/api/resource/123")

Automatic Token Refresh:

Service clients automatically refresh expired access tokens using refresh tokens when available.

Error Handling:

result = dropbox.get("/2/files/list_folder", {"path": "/Documents"})
if result is None:
    # API call failed - check logs for details
    return {"error": "Failed to access Dropbox"}

Using Services in Hooks

Every hook receives an ActorInterface that already knows about the service registry set up at application start. That means you can reference actor.services anywhere—lifecycle hooks, property hooks, method hooks, and action hooks—without extra plumbing or manual initialisation.

Action Hook Example

@app.action_hook("sync_contacts")
def sync_contacts(actor: ActorInterface, action_name: str, data: Dict[str, Any]) -> Dict[str, Any]:
    crm = actor.services.get("crm_service")

    if not crm or not crm.is_authenticated():
        return {"auth_url": crm.get_authorization_url() if crm else None}

    contacts = crm.get("/contacts", params={"limit": 50})
    return {"contacts": contacts}

Property and Method Hook Example

@app.property_hook("sales/leads")
def enrich_leads(actor: ActorInterface, operation: str, value: Dict[str, Any], path: str) -> Dict[str, Any]:
    enrichment = actor.services.get("enrichment")
    if enrichment and value:
        details = enrichment.post("/enrich", data=value)
        if details:
            value.update(details)
    return value

@app.method_hook("refresh_dashboard")
def refresh_dashboard(actor: ActorInterface, method_name: str, data: Dict[str, Any]) -> Dict[str, Any]:
    analytics = actor.services.get("analytics")
    return analytics.post("/dashboards/refresh", data=data) if analytics else {"error": "not configured"}

Key points:

  • Configure services up front using the fluent ActingWebApp API and the registry flows into every actor interface.

  • Hooks triggered via OAuth2 callbacks, factory routes, or API requests all receive the same actor.services helper—no manual dependency injection required.

Service Management

List Available Services

# Get all services and their authentication status
services_status = actor.services.list_available_services()
# Returns: {"dropbox": True, "gmail": False, "github": True}

Revoke Service Authentication

# Revoke specific service
success = actor.services.revoke_service("dropbox")

# Revoke all services
results = actor.services.revoke_all_services()
# Returns: {"dropbox": True, "gmail": True, "github": False}

REST API Endpoints

The service integration system automatically creates REST endpoints:

Service OAuth2 Callback: GET /{actor_id}/services/{service_name}/callback?code=...&state=...

Revoke Service Authentication: DELETE /{actor_id}/services/{service_name}

These endpoints are automatically configured in both Flask and FastAPI integrations.

Service Templates

Pre-configured service templates are available for popular services:

Dropbox

app.add_dropbox("client_id", "client_secret")

Scopes: files.content.read, files.metadata.read API Base URL: https://api.dropboxapi.com

Gmail

app.add_gmail("client_id", "client_secret", readonly=True)

Read-only Scopes: https://www.googleapis.com/auth/gmail.readonly Write Scopes: https://www.googleapis.com/auth/gmail.modify API Base URL: https://www.googleapis.com/gmail/v1

GitHub

app.add_github("client_id", "client_secret")

Scopes: repo, user API Base URL: https://api.github.com

Box

app.add_box("client_id", "client_secret")

Scopes: root_readwrite API Base URL: https://api.box.com/2.0

Architecture

The service integration system consists of several components:

ServiceConfig: Type-safe configuration for OAuth2 services ServiceClient: Handles authentication and API calls for a specific service ServiceRegistry: Manages registered service configurations ActorServices: Per-actor interface for accessing authenticated service clients ServicesHandler: HTTP handler for OAuth2 callbacks and service management

Token Storage: Service authentication tokens are stored securely in ActingWeb’s trust relationship system, providing per-actor isolation and proper security.

Integration: The system integrates seamlessly with both Flask and FastAPI, automatically registering the necessary routes for OAuth2 callbacks.

Security

Token Storage: Service tokens are stored in ActingWeb’s trust relationship system, providing:

  • Per-actor isolation

  • Encrypted storage

  • Secure token refresh

  • Automatic cleanup on actor deletion

OAuth2 Security: Built on the same OAuth2 foundation as user authentication:

  • State parameter validation

  • CSRF protection

  • Secure redirect URI validation

  • Token revocation support

Permissions: Service access is tied to actor permissions and trust relationships, ensuring proper authorization controls.

Troubleshooting

Service Not Registered:

service = actor.services.get("unknown_service")
# Returns None if service not registered

Authentication Failed:

Check the service configuration and ensure redirect URIs match:

# Verify service is registered
registry = app.get_service_registry()
config = registry.get_service_config("dropbox")
if not config or not config.is_enabled():
    # Service not properly configured

Token Refresh Failed:

Service clients automatically attempt token refresh. Check logs for refresh errors and ensure the service supports refresh tokens.

API Call Failed:

result = service.get("/api/endpoint")
if result is None:
    # Check logs for HTTP errors, authentication issues, etc.

Debugging:

Enable debug logging to see detailed OAuth2 flows and API calls:

import logging
logging.getLogger('actingweb').setLevel(logging.DEBUG)

This will log OAuth2 token exchanges, API requests, and error details for troubleshooting.