CVE-2024-56329
Description
Socialstream is a third-party package for Laravel Jetstream. It replaces the published authentication and profile scaffolding provided by Laravel Jetstream, with scaffolding that has support for Laravel Socialite. When linking a social account to an already authenticated user, the lack of a confirmation step introduces a security risk. This is exacerbated if ->stateless() is used in the Socialite configuration, bypassing state verification and making the exploit easier. Developers should ensure that users explicitly confirm account linking and avoid configurations that skip critical security checks. Socialstream v6.2 introduces a new custom route that requires a user to "Confirm" or "Deny" a request to link a social account. Users are advised to upgrade. There are no known workarounds for this vulnerability.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
joelbutcher/socialstreamPackagist | >= 6.0.0, < 6.2.0 | 6.2.0 |
joelbutcher/socialstreamPackagist | < 5.6.0 | 5.6.0 |
Patches
1ae4dc3906f54Merge commit from fork
5 files changed · +128 −8
resources/views/oauth/prompt.blade.php+47 −0 added@@ -0,0 +1,47 @@ +<!DOCTYPE html> +<html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + + <title>Laravel</title> + + <!-- Fonts --> + <link rel="preconnect" href="https://fonts.bunny.net"> + <link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" /> + + <!-- Styles / Scripts --> + <script src="https://cdn.tailwindcss.com"></script> +</head> +<body class="font-sans antialiased"> + <div class=" min-h-screen bg-gray-100 flex justify-center items-center dark:bg-gray-900"> + <div class="w-full sm:max-w-sm bg-white dark:bg-gray-800 overflow-hidden shadow-xl sm:rounded-lg px-6 py-4"> + <h2 class="mb-4 font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight"> + Confirm connection of your {{ \JoelButcher\Socialstream\Providers::name($provider) }} account. + </h2> + + <form method="POST" action="{{ route('oauth.callback.confirm', $provider) }}"> + @csrf + <p class="text-sm text-gray-600 dark:text-gray-400"> + @if (config('socialstream.confirmation-prompt')) + {{ config('socialstream.confirmation-prompt') }} + @else + To ensure you are the account owner of this {{ \JoelButcher\Socialstream\Providers::name($provider) }} account, + please confirm or deny the request below to link this provider to your account. + @endif + </p> + + <div class="mt-4 flex items-center justify-between"> + <button type="submit" name="result" value="deny" class="inline-flex items-center justify-center px-4 py-2 bg-red-600 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-red-500 active:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 transition ease-in-out duration-150"> + Deny + </button> + + <button type="submit" name="result" value="confirm" class="inline-flex items-center px-4 py-2 bg-gray-800 dark:bg-gray-200 border border-transparent rounded-md font-semibold text-xs text-white dark:text-gray-800 uppercase tracking-widest hover:bg-gray-700 dark:hover:bg-white focus:bg-gray-700 dark:focus:bg-white active:bg-gray-900 dark:active:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 disabled:opacity-50 transition ease-in-out duration-150"> + Confirm + </button> + </div> + </form> + </div> + </div> +</body> +</html>
routes/web.php+12 −0 added@@ -0,0 +1,12 @@ +<?php + +use Illuminate\Support\Facades\Route; +use JoelButcher\Socialstream\Http\Controllers\OAuthController; + + +Route::group(['middleware' => config('socialstream.middleware', ['web'])], function () { + Route::get('/oauth/{provider}/callback/prompt', [OAuthController::class, 'prompt'])->name('oauth.callback.prompt'); + Route::post('/oauth/{provider}/callback/confirm', [OAuthController::class, 'confirm'])->name( + 'oauth.callback.confirm' + ); +});
src/Actions/AuthenticateOAuthCallback.php+6 −8 modified@@ -65,7 +65,10 @@ public function authenticate(string $provider, ProviderUser $providerAccount): S { // If the user is authenticated, link the provider to the authenticated user. if ($user = auth()->user()) { - return $this->link($user, $provider, $providerAccount); + // cache the provider account for 10 mins whilst the user is redirected to the confirmation screen. + cache()->put("socialstream.{$user->id}:$provider.provider", $providerAccount, ttl: new \DateInterval('PT10M')); + + return redirect()->route('oauth.callback.prompt', $provider); } // Check if the user has an existing OAuth account. @@ -198,16 +201,11 @@ function ($request, $next) { ])); } - /** - * Attempt to link the provider to the authenticated user. - * - * Attempt to link the provider with the authenticated user. - */ - private function link(Authenticatable $user, string $provider, ProviderUser $providerAccount): SocialstreamResponse + public function link(Authenticatable $user, string $provider, ProviderUser $providerAccount): SocialstreamResponse { $account = Socialstream::findConnectedAccountForProviderAndId($provider, $providerAccount->getId()); - if ($account && $user?->id !== $account->user_id) { + if ($account && $user->id !== $account->user_id) { event(new OAuthProviderLinkFailed($user, $provider, $account, $providerAccount)); $this->flashError(
src/Http/Controllers/OAuthController.php+62 −0 modified@@ -2,17 +2,25 @@ namespace JoelButcher\Socialstream\Http\Controllers; +use Illuminate\Contracts\View\View; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Routing\Controller; +use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Session; +use Illuminate\Support\MessageBag; +use Illuminate\Support\ViewErrorBag; use JoelButcher\Socialstream\Contracts\AuthenticatesOAuthCallback; use JoelButcher\Socialstream\Contracts\GeneratesProviderRedirect; use JoelButcher\Socialstream\Contracts\HandlesInvalidState; use JoelButcher\Socialstream\Contracts\HandlesOAuthCallbackErrors; use JoelButcher\Socialstream\Contracts\ResolvesSocialiteUsers; use JoelButcher\Socialstream\Contracts\SocialstreamResponse; +use JoelButcher\Socialstream\Events\OAuthProviderLinkFailed; +use JoelButcher\Socialstream\Http\Responses\OAuthProviderLinkFailedResponse; +use JoelButcher\Socialstream\Providers; +use Laravel\Jetstream\Jetstream; use Laravel\Socialite\Two\InvalidStateException; use Symfony\Component\HttpFoundation\RedirectResponse as SymfonyRedirectResponse; @@ -59,4 +67,58 @@ public function callback(Request $request, string $provider): SocialstreamRespon return $this->authenticator->authenticate($provider, $providerAccount); } + + /** + * Show the oauth confirmation page. + */ + public function prompt(string $provider): View + { + return view('socialstream::oauth.prompt', [ + 'provider' => $provider, + ]); + } + + public function confirm(string $provider): SocialstreamResponse|RedirectResponse + { + $user = auth()->user(); + $providerAccount = cache()->pull("socialstream.{$user->id}:$provider.provider"); + + $result = request()->input('result'); + + if ($result === 'deny') { + event(new OAuthProviderLinkFailed($user, $provider, null, $providerAccount)); + + $this->flashError( + __('Failed to link :provider account. User denied request.', ['provider' => Providers::name($provider)]), + ); + + return app(OAuthProviderLinkFailedResponse::class); + } + + if (!$providerAccount) { + throw new \DomainException( + message: 'Could not retrieve social provider information.' + ); + } + + return $this->authenticator->link($user, $provider, $providerAccount); + } + + private function flashError(string $error): void + { + if (auth()->check()) { + if (class_exists(Jetstream::class)) { + Session::flash('flash.banner', $error); + Session::flash('flash.bannerStyle', 'danger'); + + return; + } + } + + Session::flash('errors', (new ViewErrorBag())->put( + 'default', + new MessageBag(['socialstream' => $error]) + )); + } + }
src/SocialstreamServiceProvider.php+1 −0 modified@@ -141,6 +141,7 @@ private function configureRoutes(): void 'domain' => config('socialstream.domain'), 'prefix' => config('socialstream.prefix', config('socialstream.path')), ], function () { + $this->loadRoutesFrom(path: __DIR__.'/../routes/web.php'); $this->loadRoutesFrom(path: __DIR__.'/../routes/'.config('jetstream.stack', 'livewire').'.php'); }); }
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4News mentions
0No linked articles in our index yet.