Jednoduché přihlašování přes Google

Tečník
Člen | 14
+
+2
-

Hledám nějaké jednoduché řešení, které bude schopen rozchodit i začátečník. Ideálně i s příkladem.

Našel jsem https://github.com/Kdyby/Google https://github.com/…social-login a https://github.com/martenb/google.

Šel jsem podle dokumentace a postupně řešil chyby, které vyskakovaly, ale nepodařilo se mi žádný z nich rozchodit.

jiri.pudil
Nette Blogger | 1008
+
+8
-

Používám napřímo league/oauth2-google, zhruba takto:

parameters:
	google:
		clientId: # fill in config.local.neon
		clientSecret: # fill in config.local.neon

services:
	oauth2.google.factory:
		factory: MyApp\Google\Login\GoogleProviderFactory(%google.clientId%, %google.clientSecret%)
		autowired: false

	oauth2.google:
		type: League\OAuth2\Client\Provider\Google
		factory: @oauth2.google.factory::create()
namespace MyApp\Google\Login;

use League\OAuth2\Client\Provider\Google;
use Nette\Application\LinkGenerator;

final class GoogleProviderFactory
{

	/** @var string */
	private $clientId;

	/** @var string */
	private $clientSecret;

	/** @var LinkGenerator */
	private $linkGenerator;

	public function __construct(
		string $clientId,
		string $clientSecret,
		LinkGenerator $linkGenerator
	)
	{
		$this->clientId = $clientId;
		$this->clientSecret = $clientSecret;
		$this->linkGenerator = $linkGenerator;
	}

	public function create(): Google
	{
		return new Google([
			'clientId' => $this->clientId,
			'clientSecret' => $this->clientSecret,
			'redirectUri' => $this->linkGenerator->link('Front:Login:google'),
		]);
	}

}
namespace MyApp\FrontModule;

use League\OAuth2\Client\Provider\Google;
use League\OAuth2\Client\Provider\GoogleUser;
use Nette\Application\UI\Presenter;

class LoginPresenter extends Presenter
{
	/** @var Google */
	private $google;

	public function handleGoogleLogin(): void
	{
		$authorizationUrl = $this->google->getAuthorizationUrl([
			'redirect_uri' => $this->link('//google'),
		]);

		$this->getSession(Google::class)->state = $this->google->getState();
		$this->redirectUrl($authorizationUrl);
	}


	public function actionGoogle(): void
	{
		$error = $this->getParameter('error');
		if ($error !== null) {
			$this->flashMessage('... google login error ...', 'error');
			$this->redirect('default');
		}

		$state = $this->getParameter('state');
		$stateInSession = $this->getSession(Google::class)->state;
		if ($state === null || $stateInSession === null || ! \hash_equals($stateInSession, $state)) {
			$this->flashMessage('... invalid CSRF token ...', 'error');
			$this->redirect('default');
		}

		// reset CSRF protection, it has done its job
		unset($this->getSession(Google::class)->state);

		$accessToken = $this->google->getAccessToken('authorization_code', [
			'code' => $this->getParameter('code'),
			'redirect_uri' => $this->link('//google'),
		]);

		try {
			/** @var GoogleUser $googleUser */
			$googleUser = $this->google->getResourceOwner($accessToken);
		} catch (\Throwable $e) {
			$this->flashMessage('... cannot retrieve user profile ...', 'error');
			$this->redirect('default');
		}

		$googleId = $googleUser->getId();
		if ($user = $this->userRepository->findByGoogleId($googleId)) {
			// found existing user by googleId, login and redirect
			$this->user->login($user);
			$this->redirect('Homepage:');
		}

		$googleEmail = $googleUser->getEmail();
		if ($user = $this->userRepository->findByEmail($googleEmail)) {
			// found existing user with the same email, error and force them to login using password
			$this->flashMessage('... somebody already signed up with given email ...', 'error');
			$this->redirect('default');
		}

		// new user, register them, login and redirect
		$user = $this->userRepository->registerFromGoogle($googleUser);
		$this->user->login($user);
		$this->redirect('Homepage:');
	}
}

Je potřeba dávat si pozor hlavně na to, abys měl u své aplikace v Google Developer Console nastavené všechny možné správné redirect_uris, Google je ohledně nich hodně striktní.

MajklNajt
Člen | 426
+
0
-

@jiri.pudil super návod, mňa by ešte zaujímalo, ako máš implementovaný IAuthenticator – heslo neriešiš vôbec alebo ti ho vracia userRepository?

MajklNajt
Člen | 426
+
+2
-

sorry, beriem späť, až teraz som si všimol, že User::login očakáva aj inštanciu IIdentity, kedy IAuthenticator nepoužije :)

jiri.pudil
Nette Blogger | 1008
+
+1
-

@MajklNajt heslo tady neřeším a IAuthenticator vůbec neimplementuji; do Nette\Security\User::login() lze předat rovnou identitu (kterou mi implementuje entita uživatele) a v takovém případě už se authenticator nevolá.

Tečník
Člen | 14
+
0
-

Děkuji za odpověď a velice se omlouvám, ale jsem skutečně začátečník a přiloženým souborům rozumím ještě méně než těm, které jsou v dokumentaci řešení, která jsem zmínil.

Budu rád, když mi poradíte blíže, co s nimi, nebo ještě lépe, kdyby někdo měl funkční příklad, který bych si u sebe rozbalil a mohl se podívat, co kde je.

Podařilo se mi pomocí composeru nainstalovat league/oauth2-google a mám i potřebné hodnoty clientId a clientSecret.

Pochopil jsem, co mám přidat do config.neon, bohužel ale nevím, kam nahrát druhý soubor s továrnou a pod jakým názvem a jak pak upravit cestu k němu v nastavení služby.

Chápu, že druhý soubor je presenter, který se bude používat, ale bohužel nevím, jak jej pak použít v šabloně. A nevím, co přesně dělá handleGoogleLogin().

CZechBoY
Člen | 3608
+
+1
-

Soubory umísti tam kde odpovídá cesta namespace – tzn. Factory umísti do App/Google/Login/GoogleProviderFactory.php a presenter třeba do App/Presenters.
Vesměs je jedno kam ty soubory umístíš, RobotLoader si je najde pod složkou App kdekoliv.
Celý tohle bys měl v šabloně použít asi takto (v Login šabloně):

<a n:href="googleLogin!">Přihlásit přes Google</a>
Tečník
Člen | 14
+
0
-

Díky.
Teď mi to po kliknutí hlásí Call to a member function getAuthorizationUrl() on null.
Nechybí tam někde nějaká část, kde se ta proměnná $google inicializuje?

jiri.pudil
Nette Blogger | 1008
+
+1
-

Nechybí tam někde nějaká část, kde se ta proměnná $google inicializuje?

Omlouvám se, to jsem v rámci zjednodušení příkladu vynechal.

Nejsnáze úpravou atributu v presenteru takto:

/** @var Google @inject */
public $google;

a nejčistěji vyžádáním v konstruktoru presenteru:

/** @var Google */
private $google;

public function __construct(Google $google)
{
	$this->google = $google;
}

V obou případech se o dosazení správné instance postará DI kontejner.

Editoval jiri.pudil (28. 2. 2019 14:52)

Tečník
Člen | 14
+
0
-

Díky moc, podařilo se.
Budu pokračovat pokusem navázat to na mou současnou správu uživatelů.

Tečník
Člen | 14
+
0
-

Pokouším se použít přihlašování ze sandboxu a umožnit uživateli registrovat/přihlásit se přes heslo nebo přes google.
Ale bohužel moc nevím, kudy na to.

Začal jsem tím, že jsem tabulku rozšířil o googleId a do třídy UserManager ze sandboxu přidal metodu registerFromGoogle() a ještě to bude chtít findByGoogleId() a findByEmail(). To by neměl být problém.

Otázka ale je, jak napsat dva různé autentikátory.

  • Upravit metodu authenticate ze stávajícího UserManager.php tak, aby uměla reagovat na heslo i gogleId?
  • Nebo všechno rozdělit na dvě části (UserManager a GoogleUserManager) a pouštět přihlášení podobně, jako je to popsáno v dokumentaci?

Nebo na to jdu úplně špatně?

unjustolaf
Člen | 29
+
0
-

@jiripudil

Ahoj, parádní tutoriál ale mám jednu chybu se kterou si nevím rady, při zaregistrování komponenty v configu mi vyskakuje error „Service ‚oauth2.google‘: Unknown or deprecated key ‚type‘ in definition of service.“ zkoušel jsem už všechno a nedokázal jsem na to přijít, nějaké nápady co by mohlo být špatně?

Předem děkuji.

Jakub

CZechBoY
Člen | 3608
+
+1
-

@unjustolaf zkus class misto type

MajklNajt
Člen | 426
+
+1
-

@Tečník o tomto bol práve môj príspevok – na prihlasovanie cez google nepotrebuješ vôbec authenticator, túto funkciu za teba plní google – z managera si vrátiš už rovno entitu (ktorá implementuje IIdentity), a metóde Nette\Security\User::login() dáš už len túto entitou, kedy sa authenticator obíde

jiri.pudil
Nette Blogger | 1008
+
+1
-

@unjustolaf @CZechBoY class je myslím deprecated, type ho nahrazuje, ale až od nette/di ^2.4.10

@Tečník v tom mém řešení právě žádný speciální autentikátor pro Google není potřeba. Souvisí to s tím, co jsme tu řešili s @MajklNajt – dole se volá $this->user->login($user), tj. do login metody předávám rovnou nějakou svou vlastní implementaci IIdentity. V tom případě Nette danou identitu rovnou přihlásí a autentikátor už se nevolá.

Tečník
Člen | 14
+
+2
-

Aha díky oběma, takže findByGoogleId() a registerFromGoogle() budou vracet něco jako

new Nette\Security\Identity($row[self::COLUMN_ID], $row[self::COLUMN_ROLE], $data);

A to pak půjde použít v $this->user->login($user);

cujan
Člen | 409
+
0
-

@jiripudil
zdravim skusam implementovat vas navod a neviem si rady

TypeError

Argument 1 passed to App\Google\Login\GoogleProviderFactory::__construct() must be of the type string, null given, called in

EncryptSL
Člen | 11
+
0
-

@jiripudil Ahoj a jak řešíš revoking tokenu ? Odesíláš nějaký dotaz přes Guzzle nebo něco jiného díky za případnou odpověď i když je to téma staré.

cujan
Člen | 409
+
0
-

Ako získam tieto dva parametre?
clientId: # fill in config.local.neon
clientSecret: # fill in config.local.neon

uživatel-p
Člen | 553
+
0
-

@cujan Od Googlu.
Nejspíše z Google console.

cvit84
Člen | 37
+
0
-

Zdravím.

Používám league/oauth2-google, a píše mi to: „1 passed to App\Models\Googleaccount\GoogleButton::__construct() must be of the type string, null given, called in C:\xampp\htdocs\nalezce\temp\cache\nette.configurator\Container_406aa2b1f1.php on line 615 search►“

nevím co je špatně. Myslel jsem si že stačí když budu přihlašeny na google a po kliknutí na handle to se údaje uloží. Ale píše mi to že je parametr nula. A já nevím jak tam mám dostat ty vstupní data. Může mi prosím někdo pomoct. Děkuji Vítek

cvit84
Člen | 37
+
0
-

Zdravím. nedaří se mi uložit data z google učtu. Hlasí mi to:

Argument 1 passed to Nette\Database\Table\Selection::insert() must be iterable, object given, called in /var/www/site-local/nalezce.cz/app/Models/Db/AbstractTable.php on line 54

…/site-local/nalezce.cz/app/Models/Db/AbstractTable.php:54
44: public function save($data, $id = null) {
45: if (is_null($id)) {
46: $this->findAll()->insert($data);
47: } else {
48: $this->findAll()->where($this::ID_COLUMN_NAME, $id)->update($data);
49: }
50: }
51:
52: public function saveg($data) {
53:
54: $this->findAll()->insert($data);
55:
56: }
57:
58: public function saveAll($data) {
$data
League\OAuth2\Client\Provider\GoogleUser
response: array
‚sub‘ ⇒ ‚116217675099718320994‘
‚name‘ ⇒ ‚Vít Cigánek‘
‚given_name‘ ⇒ ‚Vít‘
‚family_name‘ ⇒ ‚Cigánek‘
‚picture‘ ⇒ ‚https://lh3.googleusercontent.com/a/AATXAJycOoEzCl9l5RR9KCkpUxtXeW48yQiV6keC4G8p=s96-c‘
‚email‘ ⇒ ‚ciganekv84@gmail.com'
'email_verified‘ ⇒ true
‚locale‘ ⇒ ‚cs‘
…/www/site-local/nalezce.cz/app/Models/Db/DbUserg.php:27
…/site-local/nalezce.cz/app/Presenters/GooglePresenter.php:74
inner-code
App\Presenters\GooglePresenter::actionGoogle ()
…/nette/application/src/Application/UI/Component.php:120
…/nette/application/src/Application/UI/Presenter.php:216
…/vendor/nette/application/src/Application/Application.php:163
…/vendor/nette/application/src/Application/Application.php:90
/var/www/site-local/nalezce.cz/www/index.php:10

Nevím jak uložit pole. Podle mě to chce object. Ale v parametru mam pole. Prosím o radu.

romco
Člen | 4
+
0
-

Ahoj, skús vkladať do insert $data ako pole.
napríklad:

      $this->findAll()->insert([$data]);
petr.pavel
Člen | 532
+
0
-

Jak praví chybová hláška, předáváš objekt, akceptuje to pole.

Když se podíváš do definice GoogleUser, najdeš metodu toArray().
https://github.com/…ogleUser.php#L5

cvit84
Člen | 37
+
0
-

Děkuji za rady. To už jsem zvladl. Teď mam problém s $this->user->login($user) nevím jak udělat autentifikator aby se mi to hned neodhlasilo. Mi se ta apka chová tak že se přihlasí a hned se zase odhlasí.