Skip to main content

Overview

The Application SDK provides a robust OAuth2-based authentication system for Temporal workers using the client credentials flow. This system enables secure communication between your application and the Temporal server with automatic credential discovery and token management.

Key Features

  • Dynamic token management with intelligent refresh
  • Smart credential discovery from secret stores
  • Production-ready security best practices
  • Automatic token refresh at 80% of token lifetime
  • Support for credential rotation without restart

Authentication Components

AtlanAuthClient

Core OAuth2 token manager with automatic refresh

SecretStore

Dapr-based secret discovery and retrieval

TemporalWorkflowClient

Integrated authentication for Temporal connections

AtlanAuthClient

The core authentication component:
application_sdk/clients/atlan_auth.py
class AtlanAuthClient:
    """OAuth2 token manager for cloud service authentication.
    
    Features:
    - Automatic token acquisition and refresh
    - Smart credential discovery from secret stores
    - Dynamic refresh interval calculation
    - Credential rotation support
    """
    
    def __init__(self):
        self.application_name = APPLICATION_NAME
        self.auth_enabled: bool = AUTH_ENABLED
        self.auth_url: Optional[str] = AUTH_URL
        
        # Cached credentials and token
        self.credentials: Optional[Dict[str, str]] = None
        self._access_token: Optional[str] = None
        self._token_expiry: float = 0
The AtlanAuthClient is automatically created and managed by TemporalWorkflowClient - you typically don’t need to instantiate it directly.

Credential Discovery

The SDK uses a flexible credential discovery system:
1

Environment Variables (Primary)

Checks for ATLAN_<app_name>_client_id and ATLAN_<app_name>_client_secret
2

Secret Store (Fallback)

Falls back to Dapr secret store component if environment variables not found
3

Credential Caching

Caches discovered credentials for subsequent use

Secret Store Configuration

The secret store component can be configured to use various backends:
components/deployment-secret-store.yaml
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: deployment-secret-store
spec:
  type: secretstores.local.env
  version: v1
  metadata:
    - name: prefix
      value: "ATLAN_"
Set your credentials:
.env
ATLAN_postgres_extraction_client_id=your_client_id
ATLAN_postgres_extraction_client_secret=your_client_secret

Key Naming Convention

Credential keys must follow this format:
  • <app_name>_client_id
  • <app_name>_client_secret
App name is lowercase with hyphens converted to underscores.
Examples:
Application NameClient ID KeyClient Secret Key
postgres-extractionpostgres_extraction_client_idpostgres_extraction_client_secret
query-intelligencequery_intelligence_client_idquery_intelligence_client_secret
my-appmy_app_client_idmy_app_client_secret

Configuration

Environment Variables

.env
# Authentication settings
ATLAN_AUTH_ENABLED=true
ATLAN_AUTH_URL=https://auth.company.com/oauth/token

# Secret store configuration
ATLAN_DEPLOYMENT_SECRET_COMPONENT=deployment-secret-store
ATLAN_DEPLOYMENT_SECRET_NAME=atlan-deployment-secrets

# Temporal connection settings
ATLAN_WORKFLOW_HOST=temporal.company.com
ATLAN_WORKFLOW_PORT=7233
ATLAN_WORKFLOW_NAMESPACE=default

# Application identification
ATLAN_APPLICATION_NAME=postgres-extraction

Full Secret Structure

When using a secret store, organize all app credentials in a single secret:
atlan-deployment-secrets
{
  "postgres_extraction_client_id": "app1_client_id",
  "postgres_extraction_client_secret": "app1_secret",
  "query_intelligence_client_id": "app2_client_id",
  "query_intelligence_client_secret": "app2_secret",
  "data_pipeline_client_id": "app3_client_id",
  "data_pipeline_client_secret": "app3_secret"
}

Usage

Basic Authentication Setup

from application_sdk.clients.temporal import TemporalWorkflowClient

# Initialize client with authentication
client = TemporalWorkflowClient(
    application_name="postgres-extraction",
    # Auth settings read from environment variables
)

# Establish authenticated connection
await client.load()

# Create and run worker
worker = client.create_worker(
    activities=[my_activity],
    workflow_classes=[MyWorkflow],
)
await worker.run()
Authentication is automatic - the client handles token acquisition, refresh, and rotation.

Manual Token Management

For advanced scenarios, access the auth client directly:
from application_sdk.clients.temporal import TemporalWorkflowClient

client = TemporalWorkflowClient(application_name="my-app")
await client.load()

# Access the auth client
auth_client = client.auth_manager

# Get current token
token = await auth_client.get_access_token()

# Force token refresh
new_token = await auth_client.get_access_token(force_refresh=True)

# Get token expiry information
expiry_time = auth_client.get_token_expiry_time()
time_until_expiry = auth_client.get_time_until_expiry()

# Get authenticated headers for HTTP requests
headers = await auth_client.get_authenticated_headers()

# Use with external API calls
import aiohttp
async with aiohttp.ClientSession() as session:
    response = await session.get(
        "https://api.company.com/data",
        headers=headers
    )

Token Management

Automatic Token Refresh

The SDK implements intelligent token refresh:

Refresh Strategy

  • Refreshes at 80% of token lifetime
  • Minimum interval: 5 minutes
  • Maximum interval: 30 minutes
  • Dynamically recalculated on each refresh
  • Handles failures by clearing cache and retrying
application_sdk/clients/atlan_auth.py
def calculate_refresh_interval(self) -> int:
    """Calculate optimal token refresh interval.
    
    Returns:
        int: Refresh interval in seconds
    """
    expiry_time = self.get_token_expiry_time()
    if expiry_time:
        time_until_expiry = self.get_time_until_expiry()
        if time_until_expiry and time_until_expiry > 0:
            # Refresh at 80% of lifetime, bounded by 5-30 minutes
            refresh_interval = max(
                5 * 60,  # Minimum 5 minutes
                min(
                    30 * 60,  # Maximum 30 minutes
                    int(time_until_expiry * 0.8),  # 80% of lifetime
                ),
            )
            return refresh_interval
    
    # Default: 14 minutes
    return 14 * 60

Token Lifecycle

Authentication Flow

1

Client Initialization

TemporalWorkflowClient creates an AtlanAuthClient instance
2

Credential Discovery

  • Check environment variables first
  • Fall back to secret store if not found
  • Cache discovered credentials
3

Token Acquisition

  • Use OAuth2 client credentials flow
  • Token includes all configured scopes
  • Cache with expiry tracking
4

Temporal Connection

  • Include Bearer token in gRPC metadata
  • Establish authenticated connection
5

Dynamic Token Refresh

  • Calculate refresh interval (80% of lifetime)
  • Automatically refresh before expiry
  • Handle failures by clearing cache
  • Support credential rotation

Error Handling

Common Error Scenarios

try:
    await client.load()
except ClientError as e:
    if "AUTH_CREDENTIALS_ERROR" in str(e):
        # Credentials not found in environment or secret store
        logger.error("Check environment variables or secret store configuration")
        logger.error(f"Expected keys: {app_name}_client_id, {app_name}_client_secret")
Solutions:
  1. Verify environment variables are set correctly
  2. Check secret store component is configured
  3. Ensure secret key naming follows convention
  4. Verify Dapr sidecar is running
try:
    token = await auth_client.get_access_token()
except ClientError as e:
    if "AUTH_TOKEN_REFRESH_ERROR" in str(e):
        # Token refresh failed
        logger.error(f"Token refresh failed: {e}")
        
        # Clear cache and retry
        auth_client.clear_cache()
        token = await auth_client.get_access_token()
Common Causes:
  • Invalid credentials
  • Auth server unreachable
  • Network issues
  • Expired credentials
try:
    await client.load()
except Exception as e:
    logger.error(f"Connection failed: {e}")
    
    # Check auth configuration
    if client.auth_manager.auth_enabled:
        token_info = client.auth_manager.get_time_until_expiry()
        logger.info(f"Token expires in: {token_info}s")
Debugging Steps:
  1. Test auth URL accessibility: curl -X POST $ATLAN_AUTH_URL
  2. Verify Temporal server connectivity: telnet $ATLAN_WORKFLOW_HOST $ATLAN_WORKFLOW_PORT
  3. Check token is being included in requests (enable debug logging)

Retry with Exponential Backoff

import asyncio
from typing import Optional

async def connect_with_retry(
    client: TemporalWorkflowClient,
    max_retries: int = 3,
    base_delay: float = 2.0
) -> None:
    """Connect to Temporal with retry logic.
    
    Args:
        client: The workflow client
        max_retries: Maximum number of retry attempts
        base_delay: Base delay for exponential backoff (seconds)
    """
    for attempt in range(max_retries):
        try:
            await client.load()
            logger.info("Successfully connected to Temporal")
            return
        except Exception as e:
            if attempt < max_retries - 1:
                delay = base_delay ** attempt
                logger.warning(
                    f"Connection attempt {attempt + 1} failed: {e}. "
                    f"Retrying in {delay}s..."
                )
                
                # Clear auth cache before retry
                if hasattr(client, 'auth_manager'):
                    client.auth_manager.clear_cache()
                
                await asyncio.sleep(delay)
            else:
                logger.error(f"Failed to connect after {max_retries} attempts")
                raise

Best Practices

Use Environment Variables

Primary credentials from environment for development, secret stores for production

Never Commit Secrets

Use .env files locally (in .gitignore) and secret stores in production

Implement Retry Logic

Add exponential backoff for connection failures

Monitor Token Refresh

Track refresh frequency and failures in your observability system

Regular Rotation

Rotate credentials regularly - the SDK supports rotation without restart

Test Failure Scenarios

Test auth failures, expired tokens, and network issues

Security Checklist

1

Credentials Storage

✅ Use secret stores in production ✅ Never hardcode credentials ✅ Use IAM roles when possible
2

Network Security

✅ Use HTTPS for auth URLs ✅ Use TLS for Temporal connections ✅ Restrict network access
3

Monitoring

✅ Monitor auth failures ✅ Alert on repeated refresh failures ✅ Track token expiry times
4

Credential Rotation

✅ Implement regular rotation schedule ✅ Test rotation procedures ✅ Document rotation process

Troubleshooting

Enable Debug Logging

import logging

# Enable debug logging for auth client
logging.getLogger("application_sdk.clients.atlan_auth").setLevel(logging.DEBUG)

# Enable debug logging for secret store
logging.getLogger("application_sdk.services.secretstore").setLevel(logging.DEBUG)

Test Credential Discovery

# Test Dapr secret access
dapr invoke \
  --app-id your-app \
  --method get-secret \
  --data '{"key": "atlan-deployment-secrets"}'

# Check Dapr components
kubectl get components

# View component configuration
kubectl get component deployment-secret-store -o yaml

Verify Token

import jwt
import json

# Decode token (without verification) to inspect claims
token = await auth_client.get_access_token()
decoded = jwt.decode(token, options={"verify_signature": False})
print(json.dumps(decoded, indent=2))

Workflows

Learn about workflow implementation

Monitoring

Monitor authentication metrics

Secret Store Service

Deep dive into secret management

Error Handling

Handle authentication errors

Build docs developers (and LLMs) love