How to Implement Custom OAuth Providers Beyond GitHub in LMForge LLMOps
To implement custom OAuth providers in the LMForge platform, extend the abstract OAuth base class in api/pkg/oauth/oauth.py, implement five required methods, register the provider in OAuthService.get_all_oauth(), and configure the corresponding environment variables—no changes to routing or handlers are necessary.
The LMForge end-to-end LLMOps platform for multi-model agents ships with a pluggable authentication architecture that makes it straightforward to implement custom OAuth providers beyond the default GitHub integration. Whether you need Google, Microsoft, Gitee, or any OAuth-compatible service, the platform's generic routing system automatically exposes new providers once registered. This guide provides the exact file paths, method signatures, and code implementations required to extend the authentication system.
Understanding the Pluggable OAuth Architecture
The authentication system centers on an abstract base class OAuth defined in api/pkg/oauth/oauth.py. This class defines the contract that all providers must follow, including the OAuthUserInfo dataclass that standardizes user data across different identity sources. The OAuthHandler in api/internal/handler/oauth_handler.py and the router in api/internal/router/router.py use generic URL patterns—/oauth/<provider_name> and /oauth/authorize/<provider_name>—meaning new providers work immediately after registration without additional endpoint configuration.
Step 1: Create the Provider Class
Create a new file under api/pkg/oauth/ (for example, google_oauth.py). Your class must inherit from OAuth and implement all abstract methods to handle provider-specific authorization URLs, token exchange, and user data transformation.
Implementing the Required Methods
Every custom provider must implement five specific methods: get_provider(), get_authorization_url(), get_access_token(), get_raw_user_info(), and _transform_user_info(). The following example demonstrates a complete Google OAuth implementation:
# api/pkg/oauth/google_oauth.py
import urllib.parse
import requests
from .oauth import OAuth, OAuthUserInfo
import os
import dotenv
import certifi
dotenv.load_dotenv()
os.environ["SSL_CERT_FILE"] = certifi.where()
class GoogleOAuth(OAuth):
"""Google OAuth provider implementation for LMForge"""
_AUTHORIZE_URL = "https://accounts.google.com/o/oauth2/v2/auth"
_ACCESS_TOKEN_URL = "https://oauth2.googleapis.com/token"
_USER_INFO_URL = "https://www.googleapis.com/oauth2/v2/userinfo"
def get_provider(self) -> str:
return "google"
def get_authorization_url(self) -> str:
params = {
"client_id": self.client_id,
"redirect_uri": self.redirect_uri,
"response_type": "code",
"scope": "openid email profile",
"access_type": "offline",
"prompt": "consent",
}
return f"{self._AUTHORIZE_URL}?{urllib.parse.urlencode(params)}"
def get_access_token(self, code: str) -> str:
data = {
"client_id": self.client_id,
"client_secret": self.client_secret,
"code": code,
"grant_type": "authorization_code",
"redirect_uri": self.redirect_uri,
}
resp = requests.post(self._ACCESS_TOKEN_URL, data=data)
resp.raise_for_status()
token = resp.json().get("access_token")
if not token:
raise ValueError(f"Google OAuth failed: {resp.text}")
return token
def get_raw_user_info(self, token: str) -> dict:
headers = {"Authorization": f"Bearer {token}"}
resp = requests.get(self._USER_INFO_URL, headers=headers)
resp.raise_for_status()
return resp.json()
def _transform_user_info(self, raw_info: dict) -> OAuthUserInfo:
return OAuthUserInfo(
id=str(raw_info.get("id")),
name=raw_info.get("name") or raw_info.get("email"),
email=raw_info.get("email"),
)
Key implementation details include storing provider URLs as class constants and ensuring the OAuth scope requests at least email and profile permissions. The _transform_user_info method maps the provider's raw JSON response to the unified OAuthUserInfo dataclass, ensuring consistent user data structure regardless of the identity source.
Step 2: Configure Environment Variables
Add your provider's credentials to .env (and .env.example for documentation). The OAuthService reads these via os.getenv at runtime.
# Google OAuth credentials
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
GOOGLE_REDIRECT_URI=http://your-domain.com/oauth/authorize/google
Step 3: Register the Provider in OAuthService
Edit api/internal/service/oauth_service.py to import your new class and instantiate it within the get_all_oauth class method. This method returns a dict[str, OAuth] that maps provider names to their implementations.
# api/internal/service/oauth_service.py
from pkg.oauth import OAuth, GithubOAuth, GoogleOAuth # Import new provider
...
@classmethod
def get_all_oauth(cls) -> dict[str, OAuth]:
"""Return all integrated OAuth providers."""
github = GithubOAuth(
client_id=os.getenv("GITHUB_CLIENT_ID"),
client_secret=os.getenv("GITHUB_CLIENT_SECRET"),
redirect_uri=os.getenv("GITHUB_REDIRECT_URI"),
)
google = GoogleOAuth(
client_id=os.getenv("GOOGLE_CLIENT_ID"),
client_secret=os.getenv("GOOGLE_CLIENT_SECRET"),
redirect_uri=os.getenv("GOOGLE_REDIRECT_URI"),
)
return {
"github": github,
"google": google, # New provider registered here
}
Once added to this dictionary, the existing generic router automatically exposes endpoints at /oauth/google and /oauth/authorize/google without requiring changes to api/internal/router/router.py.
Step 4: Test the Custom OAuth Flow
Verify your implementation using HTTP requests. The flow returns an authorization URL for redirection and accepts the authorization code to exchange for a JWT matching the AuthorizeResp schema in api/internal/schema/oauth_schema.py.
# 1. Retrieve the authorization URL
curl http://localhost/oauth/google
# 2. After user authorization, exchange the code for a JWT
curl -X POST http://localhost/oauth/authorize/google \
-H "Content-Type: application/json" \
-d '{"code":"authorization_code_from_redirect"}'
A successful response contains access_token and expire_at fields, confirming the provider integration works end-to-end.
Key Source Files for OAuth Implementation
The following files comprise the authentication architecture according to the LMForge source code:
api/pkg/oauth/oauth.py— Defines the abstractOAuthbase class andOAuthUserInfodataclass.api/pkg/oauth/github_oauth.py— Reference implementation demonstrating the required pattern.api/internal/service/oauth_service.py— Central registration point for all OAuth providers viaget_all_oauth().api/internal/handler/oauth_handler.py— Generic HTTP handlers that process OAuth flows for any registered provider.api/internal/router/router.py— Configures generic URL rules that automatically support new providers..env.example— Template for required environment variables.
Summary
To implement custom OAuth providers beyond GitHub in LMForge:
- Extend
OAuthinapi/pkg/oauth/oauth.pywith provider-specific logic for authorization URLs, token exchange, and user info transformation. - Configure environment variables for client ID, secret, and redirect URI in
.env. - Register the provider in
OAuthService.get_all_oauth()located inapi/internal/service/oauth_service.py. - Leverage existing endpoints — the generic router in
api/internal/router/router.pyautomatically handles/oauth/<provider>and/oauth/authorize/<provider>without modification.
Frequently Asked Questions
What methods must I implement when adding a custom OAuth provider?
You must implement five abstract methods defined in api/pkg/oauth/oauth.py: get_provider() returns the provider name string, get_authorization_url() constructs the initial redirect URL, get_access_token() exchanges the authorization code for a bearer token, get_raw_user_info() fetches user data from the provider's API, and _transform_user_info() converts that raw data into the standard OAuthUserInfo dataclass.
Do I need to modify the router to add a new OAuth provider?
No. The router in api/internal/router/router.py already defines generic URL rules for /oauth/<string:provider_name> and /oauth/authorize/<string:provider_name>. Once you register your provider in OAuthService.get_all_oauth(), these endpoints automatically handle the new provider using the string key you specified.
How does LMForge standardize user information across different OAuth providers?
The platform uses the OAuthUserInfo dataclass defined in api/pkg/oauth/oauth.py to enforce a consistent structure containing id, name, and email. Each provider's _transform_user_info() method maps its unique API response format to this standard structure, ensuring the rest of the application receives uniform user data regardless of whether authentication occurred via GitHub, Google, or another service.
Can I implement multiple custom OAuth providers simultaneously?
Yes. The architecture supports any number of providers. Simply create separate classes for each (for example, GoogleOAuth, MicrosoftOAuth), add their respective environment variables to .env, and include all instances in the dictionary returned by OAuthService.get_all_oauth(). Each provider operates under its own URL path segment (e.g., /oauth/google, /oauth/microsoft) without conflicts.
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 →