Complete guide to implementing OAuth integrations in OpnForm with architecture overview, flow diagrams, and step-by-step implementation
This guide covers how to add new OAuth integrations to OpnForm. The system uses a modern, service-oriented architecture with intent-based OAuth flows to handle different authentication scenarios including user authentication, account integrations, and widget-based providers.
The main controller that orchestrates OAuth flows:
Copy
class OAuthController extends Controller{ public function __construct( private OAuthUserService $oauthUserService, private OAuthProviderService $oauthProviderService ) {} // Initiates OAuth flow with intent-based scoping public function redirect(Request $request, string $provider) // Handles standard OAuth callbacks public function callback(string $provider) // Handles widget-based OAuth (e.g., Telegram, Google One Tap) public function handleWidgetCallback(string $service, Request $request)}
Key Features:
Intent-based scoping: Different OAuth scopes for auth vs integration flows
Context caching: Stores integration context for callback handling
Flow determination: Automatically chooses authentication vs integration flow based on user state
Service that handles user-related OAuth operations:
Copy
class OAuthUserService{ // Handles user authentication and registration flow public function handleAuthenticationFlow(OAuthProviderService $provider, SocialiteUser $socialiteUser): JsonResponse // Finds or creates user from OAuth provider data public function findOrCreateUser(OAuthProviderService $provider, SocialiteUser $socialiteUser): User}
Service that handles provider-related OAuth operations:
Copy
class OAuthProviderService{ // Handles provider connection and integration flow public function handleIntegrationFlow(OAuthProviderService $provider, SocialiteUser $socialiteUser): JsonResponse // Creates or updates OAuth provider records public function createOrUpdateProvider(OAuthProviderService $provider, SocialiteUser $socialiteUser): OAuthProvider}
Each provider implements the OAuthDriver interface:
Copy
interface OAuthDriver{ public function getRedirectUrl(): string; public function getUser(): User; public function canCreateUser(): bool; public function getScopesForIntent(string $intent): array;}
For widget-based providers, implement WidgetOAuthDriver:
Copy
interface WidgetOAuthDriver extends OAuthDriver{ public function verifyWidgetData(array $data): bool; public function getUserFromWidgetData(array $data): array; public function isWidgetBased(): bool;}
Follow these steps to add a new OAuth provider to OpnForm:
1
Create OAuth Driver
Implement the OAuth driver class:
Copy
// api/app/Integrations/OAuth/Drivers/OAuthNewProviderDriver.php<?phpnamespace App\Integrations\OAuth\Drivers;use App\Http\Controllers\Auth\OAuthController;use App\Integrations\OAuth\Drivers\Contracts\OAuthDriver;use Laravel\Socialite\Contracts\User;use Laravel\Socialite\Facades\Socialite;class OAuthNewProviderDriver implements OAuthDriver{ private ?string $redirectUrl = null; private ?array $scopes = []; protected $provider; public function __construct() { $this->provider = Socialite::driver('newprovider'); } public function getRedirectUrl(): string { return $this->provider ->scopes($this->scopes ?? []) ->stateless() ->redirectUrl($this->redirectUrl ?? config('services.newprovider.redirect')) ->redirect() ->getTargetUrl(); } public function getUser(): User { return $this->provider ->stateless() ->redirectUrl($this->redirectUrl ?? config('services.newprovider.redirect')) ->user(); } public function canCreateUser(): bool { return true; // Set false if provider can't be used for user registration } public function setRedirectUrl(string $url): OAuthDriver { $this->redirectUrl = $url; return $this; } public function setScopes(array $scopes): OAuthDriver { $this->scopes = $scopes; return $this; } public function getScopesForIntent(string $intent): array { return match ($intent) { OAuthController::INTENT_AUTH => ['user:email', 'read:user'], OAuthController::INTENT_INTEGRATION => ['user:email', 'read:user', 'write:data'], default => ['user:email', 'read:user'], }; }}
Configure different scopes for auth vs integration intents using the OAuthController::INTENT_AUTH and OAuthController::INTENT_INTEGRATION constants. Auth scopes should be minimal (just enough for user identification), while integration scopes should include all permissions needed for the provider’s functionality.
2
Register in Provider Service
Add your new provider to the enum:
Copy
// api/app/Integrations/OAuth/OAuthProviderService.phpenum OAuthProviderService: string{ case Google = 'google'; case GoogleOneTap = 'google_one_tap'; case Stripe = 'stripe'; case Telegram = 'telegram'; case NewProvider = 'newprovider'; // Add your provider public function getDriver(): OAuthDriver { return match ($this) { self::Google => new OAuthGoogleDriver(), self::GoogleOneTap => new OAuthGoogleOneTapDriver(), self::Stripe => new OAuthStripeDriver(), self::Telegram => new OAuthTelegramDriver(), self::NewProvider => new OAuthNewProviderDriver(), // Add mapping }; } public function getDatabaseProvider(): string { return match ($this) { self::Google => 'google', self::GoogleOneTap => 'google', // Normalize to 'google' self::Stripe => 'stripe', self::Telegram => 'telegram', self::NewProvider => 'newprovider', }; }}
Important: When creating providers that represent the same OAuth service but with different authentication methods (like Google OAuth vs Google One Tap), they should be normalized to the same provider name in the database.
OpnForm handles provider normalization through the getDatabaseProvider() method in the enum:
Copy
public function getDatabaseProvider(): string{ return match ($this) { self::Google => 'google', self::GoogleOneTap => 'google', // Both normalize to 'google' self::Stripe => 'stripe', self::Telegram => 'telegram', };}
The controller automatically selects the appropriate flow based on user authentication state:
Copy
public function callback(string $provider){ $providerService = OAuthProviderService::from($provider); $socialiteUser = $providerService->getDriver()->getUser(); // Flow is determined by the user's authentication state if (Auth::check()) { // User logged in = integration flow return $this->oauthProviderService->handleIntegrationFlow($providerService, $socialiteUser); } else { // User not logged in = authentication flow return $this->oauthUserService->handleAuthenticationFlow($providerService, $socialiteUser); }}
Always verify widget data signatures cryptographically
Use minimal scopes for authentication flows
Validate all OAuth provider responses
Implement proper error handling and logging
User Experience
Provide clear loading states during OAuth flows
Handle popup blockers gracefully
Show appropriate error messages for OAuth failures
Auto-close OAuth windows when appropriate
Performance
Cache OAuth provider configurations
Use TanStack Query for efficient data fetching
Implement proper cleanup for event listeners
Minimize API calls during OAuth flows
This comprehensive architecture allows OpnForm to support any OAuth provider with a consistent, maintainable codebase that handles all the complexities of modern OAuth flows.
Assistant
Responses are generated using AI and may contain mistakes.