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
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")
)
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
ActingWebAppAPI and the registry flows into every actor interface.Hooks triggered via OAuth2 callbacks, factory routes, or API requests all receive the same
actor.serviceshelper—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.