Fixing Silent GitHub Scope Update Failures

When integrating with GitHub, users sometimes encounter issues when updating the granted permissions (scopes). A recent update addresses a scenario where these updates would silently fail, leaving users confused.

The Problem

When a user attempted to update their GitHub scope (e.g., from public-only access to all repositories) via the integrations page, a failure in the Socialite OAuth callback could lead to a confusing experience. Instead of being redirected back to the integrations page with an error message, the user was silently bounced to the dashboard, making it appear as if the update simply didn't work.

The Cause

The issue stemmed from how the application handled the OAuth callback. Specifically:

  1. Silent Redirection: Upon callback failure (e.g., a state mismatch), the application redirected the user to the login page. However, since the user was already authenticated, guest middleware redirected them to the dashboard without displaying any error message.
  2. Double Redirection: The application used EnsureUserOnOwnSubdomain, resulting in a double redirect that caused flash data (including error messages) to be lost.

The Solution

The fix involves several key changes to improve error handling and redirection:

  1. Detect Connection Flow: Before calling Socialite::driver('github')->user(), the application now checks for a 'github_connection_flow' session variable and existing authentication. This allows it to detect if the callback is part of a GitHub connection flow.

  2. Error Handling: If the connection flow is detected and the Socialite callback fails, the application now logs the error and sends a danger notification to the user, explaining that the GitHub connection failed.

  3. Direct Redirection: The application now redirects directly to the user's tenant subdomain to avoid the double redirect that was causing the loss of flash data. The redirectToIntegrations method handles this:

    protected function redirectToIntegrations(Request $request): RedirectResponse
    {
        /** @var User|null $user */
        $user = Auth::user();
    
        if ($user?->tenant) {
            return redirect()->away(
                tenantUrl($request, $user->tenant->subdomain, '/admin/integrations')
            );
        }
    
        return redirect()->route('filament.admin.pages.integrations');
    }
    
  4. Session Cleanup: The github_connection_flow and github_scope sessions are now cleared upon callback failure, preventing unexpected behavior in subsequent requests.

Testing

Comprehensive tests have been added to verify the fix and ensure that GitHub scope upgrades, downgrades, and error handling paths function correctly. These tests cover scenarios such as:

  • Connecting a GitHub account.
  • Updating the scope from public to all repos.
  • Updating the scope from all repos to public.
  • Handling callback failures gracefully.

Here's an example of a test case:

public function test_callback_failure_in_connection_flow_redirects_to_integrations(): void
{
    $user = User::factory()->create();

    $provider = Mockery::mock('Laravel\\Socialite\\Two\\AbstractProvider');
    $provider->shouldReceive('user')->andThrow(new \\Exception('OAuth error'));

    Socialite::shouldReceive('driver')->with('github')->andReturn($provider);

    $response = $this->actingAs($user)
        ->withSession([
            'github_connection_flow' => true,
            'github_scope' => GitHubScope::AllRepos->value,
        ])
        ->get(route('socialite.callback'));

    $response->assertRedirect();
    $this->assertStringContainsString('integrations', $response->getTargetUrl());

    $user->refresh();

    $this->assertNull($user->github_id);
    $this->assertNull(session('github_connection_flow'));
    $this->assertNull(session('github_scope'));
}

Key Takeaways

  • Proper error handling is crucial for a smooth user experience, especially in OAuth flows.
  • Clear and informative error messages are essential for debugging and troubleshooting.
  • Thorough testing, including error scenarios, helps prevent silent failures and ensures application stability.
  • When working with OAuth flows, carefully manage session data to avoid unexpected behavior.
Gerardo Ruiz

Gerardo Ruiz

Author

Share: