Skip to content

Handle OAuth callback errors gracefully for Google and GitHub#3

Draft
Copilot wants to merge 2 commits intomainfrom
copilot/handle-oauth-callback-errors
Draft

Handle OAuth callback errors gracefully for Google and GitHub#3
Copilot wants to merge 2 commits intomainfrom
copilot/handle-oauth-callback-errors

Conversation

Copy link
Copy Markdown

Copilot AI commented Mar 6, 2026

Unhandled ClientExceptions in the OAuth callback methods caused 500s on common failures like Google's invalid_grant (expired/reused auth code). Both callbackGoogle() and callbackGithub() now return structured JSON error responses instead.

Changes

  • callbackGoogle() / callbackGithub() — wrapped in try/catch for ClientException (→ 400) and \Exception (→ 500); Google's invalid_grant gets a distinct user-facing message
  • parseOAuthClientException() — private helper extracted to deduplicate error parsing logic across both providers; null-safe body parsing guards against exceptions with no response body
// Before — throws unhandled exception on OAuth failure
public function callbackGoogle(): JsonResponse
{
    $googleUser = Socialite::driver('google')->stateless()->user();
    return $this->extracted($googleUser);
}

// After — structured error handling
public function callbackGoogle(): JsonResponse
{
    try {
        $googleUser = Socialite::driver('google')->stateless()->user();
        return $this->extracted($googleUser);
    } catch (ClientException $e) {
        $errorCode = $this->parseOAuthClientException($e, 'Google');
        if ($errorCode === 'invalid_grant') {
            return response()->json([
                'success' => false,
                'message' => 'The authorization code has expired or has already been used. Please try signing in again.',
                'error'   => 'invalid_grant',
            ], 400);
        }
        return response()->json(['success' => false, 'message' => 'Google authentication failed. Please try again.', 'error' => $errorCode], 400);
    } catch (\Exception $e) {
        Log::error('Google OAuth callback error: ' . $e->getMessage());
        return response()->json(['success' => false, 'message' => 'An unexpected error occurred during Google authentication.'], 500);
    }
}
Original prompt

stateless() exolain this funcion

The user has attached the following files from their workspace:

  • app/Http/Controllers/V1/Auth/SocialiteMediaController.php

TITLE: Handling Google and GitHub OAuth Callback Errors in Laravel and Flutter Integration

USER INTENT:
The user wants to handle OAuth callback errors (specifically Google’s invalid_grant error) gracefully in their Laravel backend, and understand how to integrate GitHub login with a Flutter frontend, including the role of the stateless() method in Laravel Socialite.

TASK DESCRIPTION:

  • Catch and handle Google OAuth callback errors (especially invalid_grant) in Laravel, returning user-friendly JSON responses instead of 500 errors.
  • Ensure similar error handling for GitHub OAuth callbacks.
  • Explain how Flutter can integrate with GitHub login using the backend.
  • Clarify the purpose and effect of the stateless() method in Laravel Socialite.

EXISTING:

  • The Laravel backend is set up with Socialite for Google and GitHub OAuth.
  • The callbackGoogle and callbackGithub methods previously lacked error handling, resulting in unhandled exceptions and 500 errors when OAuth failed.
  • The user provided a stack trace and code snippet showing the error location in SocialiteMediaController.php.

PENDING:

  • Ensure the backend redirects to a Flutter deep link with the token after successful GitHub authentication (if using the web callback flow).
  • Confirm that the frontend and backend are aligned on the OAuth flow (either web callback or direct token exchange).
  • User may need to update Android/iOS deep link configuration for Flutter.

CODE STATE:
Files discussed/modified:

  • app/Http/Controllers/V1/Auth/SocialiteMediaController.php

Key changes:

  • Both callbackGoogle() and callbackGithub() are now wrapped in try-catch blocks to handle ClientException and other exceptions, returning appropriate JSON responses.
  • Example code for Flutter integration with GitHub OAuth was provided, using flutter_web_auth_2 and deep linking.

Relevant code snippet (Laravel):

public function callbackGoogle(): JsonResponse
{
    try {
        $googleUser = Socialite::driver('google')->stateless()->user();
        return $this->extracted($googleUser);
    } catch (ClientException $e) {
        $responseBody = json_decode((string) $e->getResponse()->getBody(), true);
        $errorCode    = $responseBody['error'] ?? 'oauth_error';
        Log::warning('Google OAuth callback failed', [
            'error'             => $errorCode,
            'error_description' => $responseBody['error_description'] ?? null,
            'status'            => $e->getCode(),
        ]);
        if ($errorCode === 'invalid_grant') {
            return response()->json([
                'success' => false,
                'message' => 'The authorization code has expired or has already been used. Please try signing in again.',
                'error'   => 'invalid_grant',
            ], 400);
        }
        return response()->json([
            'success' => false,
            'message' => 'Google authentication failed. Please try again.',
            'error'   => $errorCode,
        ], 400);
    } catch (\Exception $e) {
        Log::error('Google OAuth callback error: ' . $e->getMessage());
        return response()->json([
            'success' => false,
            'message' => 'An unexpected error occurred during Google authentication.',
        ], 500);
    }
}

Relevant code snippet (Flutter):

final String result = await FlutterWebAuth2.authenticate(
  url: githubUrl,
  callbackUrlScheme: _callbackScheme,
);
// Extract token from callback URL
final Uri callbackUri = Uri.parse(result);
final String? token = callbackUri.queryParameters['token'];

RELEVANT CODE/DOCUMENTATION SNIPPETS:

  • Laravel Socialite stateless() explanation:
    The stateless() method disables session state for the OAuth flow. Normally, Socialite stores the OAuth state in the session to prevent CSRF attacks. In API-based flows (like mobile apps or stateless backends), sessions are not used, so stateless() tells Socialite not to expect session state, allowing the OAuth flow to work without session storage.

OTHER NOTES:

  • The root cause of Google’s invalid_grant is often an expired or already-used authorization code, or a mismatch in the redirect URI.
  • For Flutter integration, the backend should support deep linking and token exchange, and the frontend should be configured to handle custom URI schemes.
  • The user was advised to check Google Cloud Console for exact redirect URI matching and to consider using a direct token exchange flow for mobile apps.

🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.

Co-authored-by: youssef-ahmed-cs <127596947+youssef-ahmed-cs@users.noreply.github.com>
Copilot AI changed the title [WIP] Handle Google and GitHub OAuth callback errors Handle OAuth callback errors gracefully for Google and GitHub Mar 6, 2026
@youssef-ahmed-cs youssef-ahmed-cs force-pushed the main branch 2 times, most recently from 9a4ec7c to ab7be91 Compare April 15, 2026 00:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants