How to Integrate Custom Authentication Providers in Claude-Code-Telegram

You can integrate custom authentication providers by implementing the AuthProvider abstract base class and registering your implementation with the AuthenticationManager during bot startup.

The RichardAtCT/claude-code-telegram repository uses a pluggable authentication architecture that supports custom authentication providers beyond the default whitelist and token-based methods. This design allows developers to integrate enterprise identity providers, OAuth2 services, or LDAP directories without modifying core bot logic.

Understanding the Pluggable Authentication Architecture

The authentication system is built around three core components defined in src/security/auth.py: an abstract base class that defines the provider contract, concrete implementations for common methods, and a manager that orchestrates authentication attempts.

The AuthProvider Abstract Base Class

Every authentication provider must inherit from AuthProvider, which defines the required interface at lines 50-60 of src/security/auth.py:

from abc import ABC, abstractmethod
from typing import Any, Dict, Optional

class AuthProvider(ABC):
    @abstractmethod
    async def authenticate(self, user_id: int, credentials: Dict[str, Any]) -> bool:
        """Validate credentials and return True if authentication succeeds."""
        pass

    @abstractmethod
    async def get_user_info(self, user_id: int) -> Optional[Dict[str, Any]]:
        """Return user metadata (permissions, roles, etc.) after authentication."""
        pass

Built-in Providers

The repository includes two reference implementations in src/security/auth.py:

  • WhitelistAuthProvider (lines 62-84): Validates users against a static list of allowed Telegram IDs
  • TokenAuthProvider (lines 92-151): Validates cryptographically signed tokens using a shared secret

These implementations demonstrate the asynchronous pattern required for custom providers.

The AuthenticationManager Orchestrator

The AuthenticationManager class (lines 163-226 of src/security/auth.py) maintains a list of providers and iterates through them during authentication attempts. It handles session creation, refresh, and expiration without requiring knowledge of specific provider implementations.

Implementing a Custom Authentication Provider

To add a custom authentication provider such as OAuth2, LDAP, or a proprietary enterprise SSO, you need to create a concrete implementation, instantiate it during bot startup, and register it with the authentication manager.

Step 1: Create the Provider Class

Create a new file (e.g., src/security/custom_oauth.py) that subclasses AuthProvider and implements the required asynchronous methods:


# src/security/custom_oauth.py

from typing import Any, Dict, Optional
from src.security.auth import AuthProvider
import httpx
import structlog

log = structlog.get_logger()

class OAuthProvider(AuthProvider):
    """
    Example OAuth2 provider that validates a JWT issued by an external IdP.
    """

    def __init__(self, jwks_url: str, allowed_aud: str):
        self.jwks_url = jwks_url
        self.allowed_aud = allowed_aud

    async def _fetch_jwks(self) -> Dict[str, Any]:
        async with httpx.AsyncClient() as client:
            resp = await client.get(self.jwks_url)
            resp.raise_for_status()
            return resp.json()

    async def authenticate(self, user_id: int, credentials: Dict[str, Any]) -> bool:
        token = credentials.get("oauth_token")
        if not token:
            log.warning("OAuth auth failed – no token", user_id=user_id)
            return False

        # Verify JWT (pseudo-code – real verification would use pyjwt / jose)

        try:
            jwks = await self._fetch_jwks()
            # ... decode JWT, check signature with jwks, verify `aud` and `sub`

            # For brevity we assume a helper `verify_jwt` exists.

            payload = verify_jwt(token, jwks, audience=self.allowed_aud)
        except Exception as exc:
            log.error("OAuth token validation error", user_id=user_id, error=str(exc))
            return False

        # The token's `sub` claim should match the Telegram user ID (optional)

        return payload.get("sub") == str(user_id)

    async def get_user_info(self, user_id: int) -> Optional[Dict[str, Any]]:
        # Return whatever info the IdP supplies; here we expose name & email.

        # In a real implementation you would call a user-info endpoint.

        return {
            "user_id": user_id,
            "auth_type": "oauth",
            "permissions": ["basic"],
        }

Step 2: Wire the Provider into the Bot

Modify src/bot/middleware/security.py to instantiate your custom provider and append it to the providers list passed to AuthenticationManager:


# src/bot/middleware/security.py (excerpt)

from src.security.auth import AuthenticationManager, WhitelistAuthProvider, TokenAuthProvider
from src.security.custom_oauth import OAuthProvider
from src.config.settings import settings

# Existing providers

providers = [
    WhitelistAuthProvider(allowed_users=settings.allowed_users, allow_all_dev=settings.dev_mode),
]

# Optional token auth (controlled by feature flag)

if settings.enable_token_auth and settings.auth_token_secret:
    providers.append(
        TokenAuthProvider(
            secret=settings.auth_token_secret,
            storage=TokenAuthProvider.InMemoryTokenStorage(),
        )
    )

# Add your custom provider here

providers.append(
    OAuthProvider(
        jwks_url="https://example-idp.com/.well-known/jwks.json",
        allowed_aud="my-telegram-bot",
    )
)

# Build the manager once; the same instance is injected into the bot's context.

auth_manager = AuthenticationManager(providers=providers)

Step 3: Use the Provider in Handlers

Telegram handlers interact with the authentication system through the manager, not individual providers. Pass credentials containing your custom provider's expected keys:


# src/bot/handlers/message.py (simplified)

async def handle_message(update, context):
    user_id = update.effective_user.id
    # Example: the client sends an OAuth token in the message text prefixed with "token:"

    if update.message.text.startswith("token:"):
        token = update.message.text.removeprefix("token:").strip()
        credentials = {"oauth_token": token}
        ok = await context.bot_data["auth_manager"].authenticate_user(
            user_id=user_id, credentials=credentials
        )
        if ok:
            await update.message.reply_text("✅ Authenticated via OAuth!")
        else:
            await update.message.reply_text("❌ OAuth token invalid.")
    else:
        # fallback to whitelist / token auth as before

        pass

Configuration and Feature Flags

The repository uses Pydantic settings to control authentication behavior. In src/config/settings.py (lines 45-49), you can enable or disable the built-in token authentication:


# src/config/settings.py

class Settings(BaseSettings):
    # ... other settings ...

    enable_token_auth: bool = False
    auth_token_secret: Optional[str] = None
    allowed_users: List[int] = []

The src/config/features.py file exposes runtime feature detection, including token_auth_enabled, which the middleware checks before instantiating the TokenAuthProvider.

Summary

  • Pluggable Architecture: The AuthProvider abstract base class in src/security/auth.py defines a clear contract for all authentication methods.
  • Simple Integration: Create a subclass implementing authenticate() and get_user_info(), then append it to the providers list in src/bot/middleware/security.py.
  • No Core Changes: The AuthenticationManager orchestrates providers without requiring modifications to handlers or session management.
  • Feature Flags: Built-in providers respect settings in src/config/settings.py, allowing you to mix custom and default authentication methods.

Frequently Asked Questions

What is the AuthProvider interface?

The AuthProvider interface is an abstract base class defined in src/security/auth.py (lines 50-60) that requires two asynchronous methods: authenticate(user_id, credentials) which returns a boolean indicating success, and get_user_info(user_id) which returns a dictionary containing user metadata such as permissions and roles.

How do I enable or disable specific providers?

You control built-in providers through Pydantic settings in src/config/settings.py. Set enable_token_auth to True or False to toggle token authentication, or modify the allowed_users list for the whitelist provider. Custom providers can be conditionally appended to the providers list in src/bot/middleware/security.py based on your own configuration logic.

Can I chain multiple authentication providers?

Yes, the AuthenticationManager accepts a list of providers and iterates through them in order during authentication attempts. The first provider that returns True from its authenticate method determines the successful authentication method. This allows you to implement fallback chains, such as checking a whitelist first, then falling back to OAuth or token validation.

Where should I store sensitive credentials for custom providers?

Store sensitive configuration such as JWKS URLs, client secrets, or API keys in environment variables and load them through src/config/settings.py using Pydantic's BaseSettings. Never hardcode secrets in your provider implementation. For runtime secrets like OAuth tokens, use the credentials dictionary passed to the authenticate method rather than storing them in the provider instance.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →