In this article, I'll show you a complete implementation for Google OAuth integration (SSO – Single Sign On) in your Symfony project. I'm using LTS version 6.4. This allows users to sign up and sign in to you with their Google Account.

Overview

We will use the Google OAuth integration with the KnpUOAuth2ClientBundle that offers good integration with Symfony Security. The solution will include the following components:

  • Installation and configuration of the required packages
  • Creating a Google OAuth Client
  • User Model Customization
  • Implementation of the Authentication Handlers
  • Frontend integration (login button))

What you should already have:

  • Basic Symfony/PHP experience
  • A Symfony project best from version 6.4
  • A ready-made login and registration process
  • A Google account

Preparatory tasks at Google

Google OAuth Setup Guide

Create a Google Cloud Console project

  1. Visit the [Google Cloud Console]]
  2. Create a new project or select an existing project
  3. Navigate to the "OAuth consent screen" in the "APIs & Services menu"“
  4. Select the user type (external or internal) and enter the required information:
    – App Name
    – User Support Email
    – Developer Contact Information
  5. Save the settings

Create OAuth credentials

  1. Navigate to "Credentials" in the "APIs & Services" menu“
  2. Click Create Credentials and select OAuth client ID)
  3. Select "Web application" as the application type)
  4. Enter a name for the client
  5. Add authorised redirect URIs:
    – For the development environment: 'http://localhost:8000/connect/google/check`
    – For the production environment: 'https://deine-domain.de/connect/google/check`
  6. Click Create)
  7. Make a note of the "Client ID" and the "Client Secret"“

Configuring Environment Variables

Add the Client ID and Client Secret to your '.env.local' file:

1

2

GOOGLE_CLIENT_ID=deine-client-id

GOOGLE_CLIENT_SECRET=dein-client-secret

Enable Google OAuth API

  1. Navigate to "Library" in the "APIs & Services" menu“
  2. Search by „Google People API
  3. Enable this API for your project

Google OAuth integration in Symfony

Installing the required packages

1

2

3

4

5

6

7

8

# Installation des KnpUOAuth2ClientBundle

composer require knpuniversity/oauth2-client-bundle

 

# Installation des Google OAuth2 Providers

composer require league/oauth2-google

 

# Falls noch nicht vorhanden, stelle sicher, dass die Symfony Security-Komponenten installiert sind

composer require symfony/security-bundle

Next, we need to set up the configuration for the OAuth2 bundle

1

2

3

4

5

6

7

8

9

# config/packages/knpu_oauth2_client.yaml

knpu_oauth2_client:

    clients:

        google:

            type: google

            client_id: '%env(GOOGLE_CLIENT_ID)%'

            client_secret: '%env(GOOGLE_CLIENT_SECRET)%'

            redirect_route: connect_google_check

            redirect_params: {}

The user model

We're now configuring our user model so that it can ingest and manage the user data provided by Google, such as name, email address, and profile picture. This means that we have to adapt our user entity. To do this, open the src/Entity/User.php and make the following changes:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

// Hier muss nullable: true hinzugefügt werden

#[ORM\Column(nullable: true)]

    private ?string $password = null;

 

// Folgende Felder müssen hinzugefügt werden

#[ORM\Column(length: 255, nullable: true)]

    private ?string $googleId = null;

 

#[ORM\Column(length: 255, nullable: true)]

    private ?string $locale = null;

 

#[ORM\Column(length: 255, nullable: true)]

    private ?string $avatarUrl = null;

     

#[ORM\Column(length: 255, nullable: true)]

    private ?string $registrationSource = null;

 

// Folgende Getter und Setter hinzufügen

public function getGoogleId(): ?string

    {

        return $this->googleId;

    }

 

public function setGoogleId(?string $googleId): static

    {

        $this->googleId = $googleId;

        return $this;

    }

 

public function getAvatarUrl(): ?string

    {

        return $this->avatarUrl;

    }

 

public function setAvatarUrl(?string $avatarUrl): static

    {

        $this->avatarUrl = $avatarUrl;

        return $this;

    }

 

public function getLocale(): ?string

    {

        return $this->locale;

    }

 

public function setLocale(?string $locale): static

    {

        $this->locale = $locale;

        return $this;

    }

 

public function getRegistrationSource(): ?string

    {

        return $this->registrationSource;

    }

 

public function setRegistrationSource(?string $registrationSource): static

    {

        $this->registrationSource = $registrationSource;

        return $this;

    }

     

public function isGoogleUser(): bool

    {

        return $this->googleId !== null;

    }

Migrating the new fields in the User Entity

1

2

3

4

5

# Migration erstellen

php bin/console make:migration

 

# Migration ausführen

php bin/console doctrine:migrations:migrate

Google Authenticator for Google OAuth

We create the following PHP file under src/Security/GoogleAuthenticator.php

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

<?php

 

namespace App\Security;

 

use App\Entity\User;

use App\Repository\UserRepository;

use Doctrine\ORM\EntityManagerInterface;

use KnpU\OAuth2ClientBundle\Client\ClientRegistry;

use KnpU\OAuth2ClientBundle\Security\Authenticator\OAuth2Authenticator;

use League\OAuth2\Client\Provider\GoogleUser;

use Symfony\Component\HttpFoundation\RedirectResponse;

use Symfony\Component\HttpFoundation\Request;

use Symfony\Component\HttpFoundation\Response;

use Symfony\Component\Routing\RouterInterface;

use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;

use Symfony\Component\Security\Core\Exception\AuthenticationException;

use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;

use Symfony\Component\Security\Http\Authenticator\Passport\Passport;

use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;

use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;

 

class GoogleAuthenticator extends OAuth2Authenticator implements AuthenticationEntryPointInterface

{

    private ClientRegistry $clientRegistry;

    private EntityManagerInterface $entityManager;

    private RouterInterface $router;

    private UserRepository $userRepository;

 

    public function __construct(

        ClientRegistry $clientRegistry,

        EntityManagerInterface $entityManager,

        RouterInterface $router,

        UserRepository $userRepository

    ) {

        $this->clientRegistry = $clientRegistry;

        $this->entityManager = $entityManager;

        $this->router = $router;

        $this->userRepository = $userRepository;

    }

 

    public function supports(Request $request): ?bool

    {

        // Prüfen, ob der aktuelle Request der Google-Callback ist

        return $request->attributes->get('_route') === 'connect_google_check';

    }

 

    public function authenticate(Request $request): Passport

    {

        $client = $this->clientRegistry->getClient('google');

        $accessToken = $this->fetchAccessToken($client);

 

        return new SelfValidatingPassport(

            new UserBadge($accessToken->getToken(), function() use ($accessToken, $client) {

                /** @var GoogleUser $googleUser */

                $googleUser = $client->fetchUserFromToken($accessToken);

 

                // Email ist die wichtigste Information

                $email = $googleUser->getEmail();

 

                // Prüfen, ob der User bereits existiert

                $existingUser = $this->userRepository->findOneBy(['email' => $email]);

 

                if ($existingUser) {

                    // User existiert bereits

                    if (!$existingUser->getGoogleId()) {

                        // Nur wenn der User noch nicht mit Google verbunden ist, setzen wir die Google-ID

                        $existingUser->setGoogleId($googleUser->getId());

                        $existingUser->setRegistrationSource('google');

                    }

 

                    // Hier: Immer die Benutzerdaten aktualisieren, unabhängig davon, ob die Google-ID bereits gesetzt war

                    $this->updateUserFromGoogleData($existingUser, $googleUser);

                    $this->entityManager->persist($existingUser);

                    $this->entityManager->flush();

 

                    return $existingUser;

                }

 

                // Neuen User erstellen

                $user = new User();

                $user->setEmail($email);

                $user->setGoogleId($googleUser->getId());

                $user->setRegistrationSource('google');

                $user->setPassword($password ?? '');

                $user->setRoles(['ROLE_USER']);

                $this->updateUserFromGoogleData($user, $googleUser);

                 

                $this->entityManager->persist($user);

                $this->entityManager->flush();

 

                return $user;

            })

        );

    }

 

    private function updateUserFromGoogleData(User $user, GoogleUser $googleUser): void

    {

        // Name kann als "given_name" und "family_name" oder in "name" enthalten sein

        $user->setFirstName($googleUser->getFirstName());

        $user->setLastName($googleUser->getLastName());

         

        // Avatar-URL und Locale, falls verfügbar

        if (method_exists($googleUser, 'getAvatar')) {

            $user->setAvatarUrl($googleUser->getAvatar());

        }

         

        if (method_exists($googleUser, 'getLocale')) {

            $user->setLocale($googleUser->getLocale());

        }

    }

 

    public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response

    {

        // Weiterleitung nach erfolgreicher Authentifizierung

        return new RedirectResponse($this->router->generate('app_homepage'));

    }

 

    public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response

    {

        $message = strtr($exception->getMessageKey(), $exception->getMessageData());

 

        return new Response($message, Response::HTTP_FORBIDDEN);

    }

     

    public function start(Request $request, AuthenticationException $authException = null): Response

    {

        return new RedirectResponse(

            $this->router->generate('app_login'),

            Response::HTTP_TEMPORARY_REDIRECT

        );

    }

}

Adjust the corresponding routes if they are different from yours.

Controller for Google OAuth integration

As another important step, we need a controller:

1

php bin/console make:controller Google

We add the following code to the GoogleController.php File:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

<?php

 

namespace App\Controller;

 

use KnpU\OAuth2ClientBundle\Client\ClientRegistry;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;

use Symfony\Component\HttpFoundation\RedirectResponse;

use Symfony\Component\HttpFoundation\Request;

use Symfony\Component\Routing\Annotation\Route;

 

class GoogleController extends AbstractController

{

    /**

     * Link zur Initiierung des OAuth-Flows mit Google

     */

    #[Route('/connect/google', name: 'connect_google')]

    public function connectAction(ClientRegistry $clientRegistry): RedirectResponse

    {

        // Weiterleitung zur Google Authorization URL

        return $clientRegistry

            ->getClient('google')

            ->redirect([

                'profile',

                'email' // Die benötigten Scopes

            ]);

    }

 

    /**

     * Diese Route wird nach erfolgreicher Authentifizierung bei Google aufgerufen

     * Sie wird vom Google-Authenticator behandelt und nicht von dieser Methode

     */

    #[Route('/connect/google/check', name: 'connect_google_check')]

    public function connectCheckAction(Request $request, ClientRegistry $clientRegistry)

    {

        // Diese Methode wird nie aufgerufen, da der GoogleAuthenticator

        // die Anfrage abfängt und verarbeitet

        return $this->redirectToRoute('app_homepage');

    }

}

Adjust the appropriate route here as well.

Customizing the Security Configuration

It's time to integrate our GoogleAuthenticator. To do this, we open our security.yaml under config/packages/security.yaml and add the following lines:

1

2

3

4

5

6

7

8

security:

    firewalls:

        main:

            # Google OAuth-Authenticator aktivieren

            custom_authenticators:

                - App\Security\GoogleAuthenticator

 

            entry_point: form_login

Google Login Button

In the login and registration template, we then add the button for Google authentication.

1

2

3

4

5

6

7

8

{# templates/security/login.html.twig #}

{# templates/registration/register.html.twig #}

 

<div class="text-center my-4">

    <a href="{{ path('connect_google') }}" class="btn btn-outline-danger w-100 mb-3 d-flex align-items-center justify-content-center">

         Mit Google anmelden

    </a>

</div>

Summary of Google OAuth integration for Symfony

We have now created a complete implementation for the Google OAuth integration in your Symfony project. Here's an overview of the implementation:

Installed Packages

  • KnpUOAuth2ClientBundle for OAuth2 integration
  • League/OAuth2-Google as Google OAuth2 Provider

Main Components

  1. User Entity with Google Support:
    • Stores important Google information such as googleId, profile picture, name
    • Supports both traditional password authentication and Google OAuth
  2. GoogleAuthenticator:
    • Processes the Google OAuth flow
    • Creates new users or connects existing accounts
    • Handles authentication for the security component
  3. Controller for login and Google connection:
    • GoogleController with routes for OAuth initiation and callback
    • SecurityController for classic login
    • RegistrationController for manual registration
  4. Templates with Google Button:
    • Login and registration pages with Google login button

Functionalities

  • Single Sign-On (SSO) with Google:
    • Users can log in with one click using their Google account
    • Profile data is taken from the Google account
  • Automatic Account Linking:
    • If a user with the same email address already exists, their account will be linked to Google
  • Hybrid authentication:
    • Supports both traditional email/password authentication and Google OAuth
    • Saves the registration source (Google or Form)

Tips for production environments

  • Make sure HTTPS connections are secure
  • Make sure redirect URLs are configured correctly in the Google Cloud Console
  • For high user numbers (>100), full verification of the Google project is required

This implementation is future-proof and can be easily extended to include other OAuth providers. User information is stored consistently, regardless of whether a user logs in through Google or registers traditionally.

If you have any questions or get stuck at one point, feel free to write your experiences in the comments.