Closed selfsimilar closed 2 months ago
If Google is making a request to the application that makes me think the OAuth flow is either successful and there's a redirect happening elsewhere in your application, or that the initial request and callback differs in state verification (try making the socialite requests "stateless" and try setting the access_type
option to offline
).
If neither of these work, I will need you to link to a valid reproduction repository, otherwise I don't have the time spare to investigate this, especially as I have two apps currently in production using Socialtream version 6.x with Google that work absolutely fine.
I had another big wall of text with more diagnostic information, but I realize I should mention this is part of upgrading Laravel from 10.x to 11.x, and maybe that is a confounding issue?
Wall 'o Text:
The Google SSO was already set to be "stateless", but I did try it with the default "stateful"(?) resolver and it also failed. What's interesting is that after a "successful" SSO login if I just use the browser back button to the login screen, I'll encounter a "wrong password or username" error which is the "failed" auth text. But I don't see that if I just straight navigate to my login page from the Google profile page that I'm redirected to.
I also added some logging to my custom ResolveSocialiteUser
class that looks like this:
<?php
namespace App\Actions\Socialstream;
use Illuminate\Support\Facades\Log;
use JoelButcher\Socialstream\Contracts\ResolvesSocialiteUsers;
use JoelButcher\Socialstream\Socialstream;
use Laravel\Socialite\Contracts\User;
use Laravel\Socialite\Facades\Socialite;
class ResolveSocialiteUser implements ResolvesSocialiteUsers
{
/**
* Resolve the user for a given provider.
*/
public function resolve(string $provider): User
{
try {
$socialiteDriver = Socialite::driver($provider);
$user = match ($provider) {
Providers::google() => $socialiteDriver->stateless()->user(),
default => $socialiteDriver->user(),
};
Log::debug("SSO found user", ['uid' => $user->id, 'name' => $user->name, 'email' => $user->email]);
} catch (Exception $e) {
Log::error('SSO Fail', $e);
session()->flash('flash.banner', __('Unable to log you in. Please try again.'));
return redirect('/login');
}
if (Socialstream::generatesMissingEmails()) {
$user->email = $user->getEmail() ?? ("{$user->id}@{$provider}".config('app.domain'));
}
return $user;
}
}
The logs then show:
{
"uid": "01234567890123456789",
"name": "SSO User",
"email": "example@gmail.com"
}
where the $user->id
corresponds to the provider_id
in the connected_accounts
table, not the user_id
column. This is probably right, and it definitely seems like Socialstream/Socialite is able to retrieve the right user from the SSO. So a lot of this is still working.
Also, @joelbutcher you say "try setting the access_type option to offline" but I'm confused. I don't see access_type
referenced anywhere in the Socialstream or Socialite codebase when I grep my vendor directory. Is this a setting I should be looking for in the Google SSO settings in GCP?
@selfsimilar yes, this is a Google OAuth parameter, nothing to do with Laravel or Socialstream
I can't log in from any provider. What's going on?
[2024-09-06 14:37:34] local.ERROR: {"exception":"[object] (Laravel\Socialite\Two\InvalidStateException(code: 0): at /var/www/html/vendor/laravel/socialite/src/Two/AbstractProvider.php:237) [stacktrace]
@majweb the hint is in the name of the exception thrown by Laravel Socialite:
The state that's used to verify the callback request is for the same session that requested the redirect to the OAuth provider. Call ->stateless()
in the generate redirect request in the resolve socialite user classes
where exactly? Because here I have ->stateless() on the screen Providers::google.....
If I add $socialiteDriver->stateless()->user() still not working
If it doesn't throw this error, it goes through but doesn't log in the user. The first time it saves to the connected table with this error anyway. If it already exists in the table, it will not log in the next time. These are conclusions from observations.
Fresh installation. Laravel 11 with the current Social Stream package. (Inertiajs Vuejs) Well configured. After selecting the provider, it does not log in the user and does not redirect to the panel.
@majweb I don't get this problem on a fresh install here:
Can you share the code on github? After logging in, you get 403 but it's probably from a non-existent route.
What could be the reason why it doesn't log me in as a user? How is it that the same fresh applications don't work the same? Maybe something in config/session.php. The best thing is that everything worked before.
@majweb I linked the repo on my previous comment
Looks like the issue I have is because filament is also installed
Repo not found
Ah, didn't realise it was private. Have made it public
Please look abctest.pl Select login and account type as worker (then socials appear) and try to log in via github. Please log in twice because the first time it works, the second time it doesn't.
You're using v6.1.5. Update your composer deps to use 6.1.6 minimum version
Unfortunately, the problem persists. I have version 6.1.6 and it still does not log me in. What else could be the problem?
public function resolve(string $provider): User
{
try {
$socialiteDriver = Socialite::driver($provider);
$user = match ($provider) {
Providers::google() => $socialiteDriver->stateless()->user(),
Providers::linkedinOpenId() => $socialiteDriver->stateless()->user(),
default => $socialiteDriver->stateless()->user(),
};
Log::debug("SSO found user", ['uid' => $user->id, 'name' => $user->name, 'email' => $user->email]);
} catch (Exception $e) {
Log::error('SSO Fail', $e);
session()->flash('flash.banner', __('Unable to log you in. Please try again.'));
return redirect('/login');
}
[2024-09-17 06:21:18] production.DEBUG: SSO found user {"uid":22354155,"name":"xxxx xxxxx","email":"xxx@xxx.pl"}
The user is return why he is not logging in?
@majweb if that code is from your project, you're not returning the user from that method inside the try
block
@majweb I also can't use that repo as an appropriate reproduction repo to investigate this further, as I don't have a license for spatie's media library
Bottom I return user.
You don't need install media library
You don't need install media library
Not entirely true, some of your form requests use it and your JS-deps require it as well, build fails without it.
I can't replicate this on a fresh Laravel install, and I don't have the time to debug your specific use case using the linked reproduction repo.
Please either update your repo so that I can install all deps without being blocked by a paywall or remove these deps from the project so I can install and build your app to provide further assistance.
Until I can easily build and run your application locally without errors due to paid packages, I'm closing this issue.
ok, I'll take care of it
https://github.com/majweb/work repo is ok
@majweb Getting this
error during build:
[vite]: Rollup failed to resolve import "@spatie/media-library-pro-vue3-attachment" from "/Users/joel/Projects/majweb/work/resources/js/Pages/Buy/Article/Create.vue".
This is most likely unintended because it can break your application at runtime.
If you do want to externalize this module explicitly add it to
`build.rollupOptions.external`
Ok i Fix imports. Now must be good.
@majweb the issue is you're setting a password for the user when they register. Socialstream registers a custom user provider for Fortify to validateCredentials
which verifies that the password is null and will attempt to authenticate the user if the given provider account matches the user that has been found in the database with the same email. Socialstream requires the users password to be NULL
In CreateUserFromProvider
remove line 38 and this should work
Unfortunately, this problem still exists. When logging in for the first time, there is no problem, when I want to log in again, I don't log in again. Please try to log in twice.
@majweb this issue is with using Fortify::authenticateUsing()
callback. It's not very well documented (which I'll fix).
I suggest changing this code: https://github.com/majweb/work/blob/1f176e67ae7595b46ce86197fba88e99091b142a/app/Providers/FortifyServiceProvider.php#L34-L43
to something like:
Fortify::authenticateUsing(function (Request $request) {
$user = null;
$provider = $request->route('provider');
// 1a. Attempt the resolve the user via socialstream
if ($provider) {
$socialUser = app(ResolvesSocialiteUsers::class)
->resolve($provider);
$connectedAccount = Socialstream::$connectedAccountModel::where('email', $socialUser->getEmail())->first();
if (! $connectedAccount) {
throw ValidationException::withMessages([
Fortify::username() => [__('auth.failed')],
]);
}
$user = $connectedAccount->user;
}
// 1b. Attempt to resolve the user if email present in request (i.e. from login form).
if (! $user && $request->has('email') {
$user = User::where('email', $request->email)->first();
}
// 2. Check if the resolved user is blocked and handle
if ($user->blockedByAdmin()) {
throw ValidationException::withMessages([
Fortify::username() => [__('auth.blocked')],
]);
}
// 3. User is not blocked, log in if from Socialstream route
if ($provider) {
return $user;
}
// 4. User hasn't set a password, so must login using an OAuth provider
if (is_null($user->password) {
throw ValidationException::withMessages([
Fortify::username() => [__('auth.failed')],
]);
}
// 5. Verify the password if the user has logged in via a form
return Hash::check($request->password, $user->password) ? $user : null;
});
Dev guide and explanation here
Bad link to guide
Ok according to the instructions. I created a folder and class app/Auth/SocialstreamUserProvider
<?php
declare(strict_types=1);
namespace JoelButcher\Socialstream\Auth;
use Illuminate\Auth\EloquentUserProvider;
use Illuminate\Contracts\Auth\Authenticatable as UserContract;
class SocialstreamUserProvider extends EloquentUserProvider
{
public function validateCredentials(UserContract $user, #[\SensitiveParameter] array $credentials): bool
{
if (is_null($user->getAuthPassword())) {
return false;
}
return parent::validateCredentials($user, $credentials);
}
}
I registered at Providers/SocialstreamServiceProvider.php
<?php
namespace App\Providers;
use App\Actions\Socialstream\CreateConnectedAccount;
use App\Actions\Socialstream\CreateUserFromProvider;
use App\Actions\Socialstream\GenerateRedirectForProvider;
use App\Actions\Socialstream\HandleInvalidState;
use App\Actions\Socialstream\ResolveSocialiteUser;
use App\Actions\Socialstream\UpdateConnectedAccount;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\ServiceProvider;
use JoelButcher\Socialstream\Auth\SocialstreamUserProvider;
use JoelButcher\Socialstream\Concerns\ConfirmsFilament;
use JoelButcher\Socialstream\Socialstream;
use Laravel\Fortify\Fortify;
class SocialstreamServiceProvider extends ServiceProvider
{
use ConfirmsFilament;
/**
* Register any application services.
*/
public function register(): void
{
//
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
$this->configureAuth();
Socialstream::resolvesSocialiteUsersUsing(ResolveSocialiteUser::class);
Socialstream::createUsersFromProviderUsing(CreateUserFromProvider::class);
Socialstream::createConnectedAccountsUsing(CreateConnectedAccount::class);
Socialstream::updateConnectedAccountsUsing(UpdateConnectedAccount::class);
Socialstream::handlesInvalidStateUsing(HandleInvalidState::class);
Socialstream::generatesProvidersRedirectsUsing(GenerateRedirectForProvider::class);
}
private function configureAuth(): void
{
Auth::provider('eloquent', fn ($app, array $config) => new SocialstreamUserProvider(
hasher: $app['hash'],
model: $config['model']
));
}
}
And I changed the auth pipeline
Fortify::authenticateUsing(function (Request $request) {
$user = null;
$provider = $request->route('provider');
// 1a. Attempt the resolve the user via socialstream
if ($provider) {
$socialUser = app(ResolvesSocialiteUsers::class)
->resolve($provider);
$connectedAccount = Socialstream::$connectedAccountModel::where('email', $socialUser->getEmail())->first();
if (! $connectedAccount) {
throw ValidationException::withMessages([
Fortify::username() => [__('auth.failed')],
]);
}
$user = $connectedAccount->user;
}
// 1b. Attempt to resolve the user if email present in request (i.e. from login form).
if (! $user && $request->has('email')) {
$user = User::where('email', $request->email)->first();
}
// 2. Check if the resolved user is blocked and handle
if ($user->blockedByAdmin()) {
throw ValidationException::withMessages([
Fortify::username() => [__('auth.blocked')],
]);
}
// 3. User is not blocked, log in if from Socialstream route
if ($provider) {
return $user;
}
// 4. User hasn't set a password, so must login using an OAuth provider
if (is_null($user->password)) {
throw ValidationException::withMessages([
Fortify::username() => [__('auth.failed')],
]);
}
// 5. Verify the password if the user has logged in via a form
return Hash::check($request->password, $user->password) ? $user : null;
});
Are these all the steps?
?
@majweb does this work for you? Are you on the latest version of 6.x? If this works then yes. If not, lemme know and I'll try to carve out some time next week.
It works, but I have a question whether I implemented it correctly.
To me it looks like you have done exactly what's needed to me 🙂
I found bug look topic Overriding Fortify's Authentication custom LoginRequest #385
Stack
Jetstream – Livewire
Package Version
v6.1.5
Laravel Version
v11
Livewire Version
v3.5.6
react Version
No response
Vue Version
No response
PHP Version
PHP 8.3
Problem description
After upgrading SocialStream from v5 to v6, SSO login (specifically Google) now redirects from the "Choose an account" page to the Google account page (myaccount.google.com) instead of back to the application. I don't see where in the upgrade guide that a setting needs to be changed, but I would guess that this is a configuration issue. I see in the log files that a request IS being made to the application after choosing the account, but the result is incorrect.
Expected behavior
After choosing your Google account, you should be redirected back to the application.
Steps to reproduce
This may not be reproducible without more knowledge of config settings.
Reproduction repository
https://github.com/no-example-yet
Relevant log output
No response