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 IDsTokenAuthProvider(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
AuthProviderabstract base class insrc/security/auth.pydefines a clear contract for all authentication methods. - Simple Integration: Create a subclass implementing
authenticate()andget_user_info(), then append it to the providers list insrc/bot/middleware/security.py. - No Core Changes: The
AuthenticationManagerorchestrates 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →