Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade from 5.x to 6.x breaks login #370

Closed
selfsimilar opened this issue Aug 29, 2024 · 40 comments
Closed

Upgrade from 5.x to 6.x breaks login #370

selfsimilar opened this issue Aug 29, 2024 · 40 comments
Labels
bug Something isn't working

Comments

@selfsimilar
Copy link
Contributor

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

@selfsimilar selfsimilar added the bug Something isn't working label Aug 29, 2024
@joelbutcher
Copy link
Owner

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.

@selfsimilar
Copy link
Contributor Author

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?

@selfsimilar
Copy link
Contributor Author

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": "[email protected]"
}

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.

@selfsimilar
Copy link
Contributor Author

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?

@joelbutcher
Copy link
Owner

@selfsimilar yes, this is a Google OAuth parameter, nothing to do with Laravel or Socialstream

@majweb
Copy link

majweb commented Sep 6, 2024

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]
#0 /var/www/html/app/Actions/Socialstream/ResolveSocialiteUser.php(23): Laravel\Socialite\Two\AbstractProvider->user()
#1 /var/www/html/vendor/joelbutcher/socialstream/src/Http/Controllers/OAuthController.php(55): App\Actions\Socialstream\ResolveSocialiteUser->resolve()........

chrome_4I59yv1IpB

@joelbutcher
Copy link
Owner

@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

@majweb
Copy link

majweb commented Sep 7, 2024

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.

@majweb
Copy link

majweb commented Sep 9, 2024

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.

@joelbutcher
Copy link
Owner

@majweb I don't get this problem on a fresh install here:

joelbutcher/socialstream-demo

https://demo.socialstream.dev

@majweb
Copy link

majweb commented Sep 11, 2024

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
Copy link

majweb commented Sep 11, 2024

Welcome.-.Work4you.global.-.Google.Chrome.2024-09-11.08-19-46.mp4

Look

@joelbutcher
Copy link
Owner

@majweb I linked the repo on my previous comment

@joelbutcher
Copy link
Owner

Looks like the issue I have is because filament is also installed

@majweb
Copy link

majweb commented Sep 11, 2024

chrome_hPPnAi4NSU

Repo not found

@joelbutcher
Copy link
Owner

Ah, didn't realise it was private. Have made it public

@majweb
Copy link

majweb commented Sep 16, 2024

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.

repo:https://github.com/majweb/work

@joelbutcher
Copy link
Owner

You're using v6.1.5. Update your composer deps to use 6.1.6 minimum version

@majweb
Copy link

majweb commented Sep 17, 2024

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":"[email protected]"}

The user is return why he is not logging in?

@joelbutcher
Copy link
Owner

joelbutcher commented Sep 17, 2024

@majweb if that code is from your project, you're not returning the user from that method inside the try block

@joelbutcher
Copy link
Owner

@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

@majweb
Copy link

majweb commented Sep 17, 2024

Screenshot_20240917_205237_Chrome

Bottom I return user.

You don't need install media library

@joelbutcher
Copy link
Owner

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.

@joelbutcher
Copy link
Owner

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.

@joelbutcher joelbutcher closed this as not planned Won't fix, can't repro, duplicate, stale Sep 18, 2024
@majweb
Copy link

majweb commented Sep 19, 2024

ok, I'll take care of it

@majweb
Copy link

majweb commented Sep 19, 2024

https://github.com/majweb/work repo is ok

@joelbutcher
Copy link
Owner

@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`

@majweb
Copy link

majweb commented Sep 19, 2024

Ok i Fix imports. Now must be good.

@joelbutcher
Copy link
Owner

joelbutcher commented Sep 19, 2024

@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

@majweb
Copy link

majweb commented Sep 20, 2024

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.

@joelbutcher
Copy link
Owner

joelbutcher commented Sep 20, 2024

@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;
});

@joelbutcher
Copy link
Owner

Dev guide and explanation here

@majweb
Copy link

majweb commented Sep 21, 2024

Bad link to guide

@joelbutcher
Copy link
Owner

@majweb
Copy link

majweb commented Sep 25, 2024

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
Copy link

majweb commented Oct 10, 2024

?

@joelbutcher
Copy link
Owner

@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.

@majweb
Copy link

majweb commented Oct 14, 2024

It works, but I have a question whether I implemented it correctly.

@joelbutcher
Copy link
Owner

To me it looks like you have done exactly what's needed to me 🙂

@majweb
Copy link

majweb commented Nov 13, 2024

I found bug look topic Overriding Fortify's Authentication custom LoginRequest #385

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants