|
| 1 | +Step 4: Configuring the connect (register) layer |
| 2 | +================================================ |
| 3 | + |
| 4 | +### A) Create the `Connector` class |
| 5 | + |
| 6 | +We need to create new class that will implement `HWI\Bundle\OAuthBundle\Connect\AccountConnectorInterface` interface. |
| 7 | +That class will be responsible for persisting `User` entities with given resource owner identifiers. For simple implementation |
| 8 | +we can define the class as: |
| 9 | +```yaml |
| 10 | +services: |
| 11 | + App\Security\OAuthConnector: |
| 12 | + arguments: |
| 13 | + $properties: |
| 14 | + 'facebook': 'facebook' |
| 15 | + 'google': 'google' |
| 16 | +``` |
| 17 | +And implement it: |
| 18 | +```php |
| 19 | +namespace App\Security; |
| 20 | + |
| 21 | +final class OAuthConnector implements AccountConnectorInterface |
| 22 | +{ |
| 23 | + public function __construct( |
| 24 | + private readonly EntityManagerInterface $entityManager, |
| 25 | + private readonly array $properties |
| 26 | + ) { |
| 27 | + } |
| 28 | + |
| 29 | + public function connect(UserInterface $user, UserResponseInterface $response) |
| 30 | + { |
| 31 | + if (!isset($this->properties[$response->getResourceOwner()->getName()])) { |
| 32 | + return; |
| 33 | + } |
| 34 | + |
| 35 | + $property = new PropertyAccessor(); |
| 36 | + $property->setValue($user, $this->properties[$response->getResourceOwner()->getName()], $response->getUserIdentifier()); |
| 37 | + |
| 38 | + $this->entityManager->persist($user); |
| 39 | + $this->entityManager->flush(); |
| 40 | + } |
| 41 | +} |
| 42 | +``` |
| 43 | + |
| 44 | +### B) Create form & form handler |
| 45 | + |
| 46 | +In order to properly display & handle form of user registration, we need to create two classes: form & form handler. |
| 47 | +The first one will be Symfony Form, let's call it `RegistrationFormType`: |
| 48 | +```php |
| 49 | +namespace App\Form; |
| 50 | + |
| 51 | +final class RegistrationFormType extends AbstractType |
| 52 | +{ |
| 53 | + public function buildForm(FormBuilderInterface $builder, array $options): void |
| 54 | + { |
| 55 | + $builder |
| 56 | + ->add('firstName') |
| 57 | + ->add('lastName') |
| 58 | + ->add('email') |
| 59 | + ->add('username', TextType::class, ['required' => false]) |
| 60 | + ->add('agreeTerms', CheckboxType::class, [ |
| 61 | + 'mapped' => false, |
| 62 | + 'constraints' => [ |
| 63 | + new IsTrue([ |
| 64 | + 'message' => 'You should agree to our terms.', |
| 65 | + ]), |
| 66 | + ], |
| 67 | + ]) |
| 68 | + ->add('plainPassword', PasswordType::class, [ |
| 69 | + // instead of being set onto the object directly, |
| 70 | + // this is read and encoded in the handler |
| 71 | + 'mapped' => false, |
| 72 | + 'attr' => ['autocomplete' => 'new-password'], |
| 73 | + 'constraints' => [ |
| 74 | + new NotBlank([ |
| 75 | + 'message' => 'Please enter a password', |
| 76 | + ]), |
| 77 | + new Length([ |
| 78 | + 'min' => 6, |
| 79 | + 'minMessage' => 'Your password should be at least {{ limit }} characters', |
| 80 | + // max length allowed by Symfony for security reasons |
| 81 | + 'max' => 4096, |
| 82 | + ]), |
| 83 | + ], |
| 84 | + ]) |
| 85 | + ; |
| 86 | + } |
| 87 | + |
| 88 | + public function configureOptions(OptionsResolver $resolver): void |
| 89 | + { |
| 90 | + $resolver->setDefaults([ |
| 91 | + 'data_class' => User::class, |
| 92 | + ]); |
| 93 | + } |
| 94 | +} |
| 95 | +``` |
| 96 | +Now we need to handle the form data, to do that we need new class that implements `HWI\Bundle\OAuthBundle\Form\RegistrationFormHandlerInterface` interface: |
| 97 | +```php |
| 98 | +namespace App\Security; |
| 99 | + |
| 100 | +final readonly class FormHandler implements RegistrationFormHandlerInterface |
| 101 | +{ |
| 102 | + public function __construct( |
| 103 | + private UserPasswordHasherInterface $userPasswordHasher |
| 104 | + ) { |
| 105 | + } |
| 106 | + |
| 107 | + public function process(Request $request, FormInterface $form, UserResponseInterface $userInformation): bool |
| 108 | + { |
| 109 | + $user = new User(); |
| 110 | + $user->setEmail($userInformation->getEmail()); |
| 111 | + $user->setUsername($userInformation->getNickname()); |
| 112 | + $user->setFirstName($userInformation->getFirstName()); |
| 113 | + $user->setLastName($userInformation->getLastName()); |
| 114 | + |
| 115 | + $form->setData($user); |
| 116 | + $form->handleRequest($request); |
| 117 | + |
| 118 | + if ($form->isSubmitted() && $form->isValid()) { |
| 119 | + // encode the plain password |
| 120 | + $user->setPassword( |
| 121 | + $this->userPasswordHasher->hashPassword( |
| 122 | + $user, |
| 123 | + $form->get('plainPassword')->getData() |
| 124 | + ) |
| 125 | + ); |
| 126 | + |
| 127 | + return true; |
| 128 | + } |
| 129 | + |
| 130 | + return false; |
| 131 | + } |
| 132 | +} |
| 133 | +``` |
| 134 | + |
| 135 | +### C) Configure connect functionality |
| 136 | + |
| 137 | +As final step, we need to enable connect functionality both in the bundle & on the security firewall: |
| 138 | +```yaml |
| 139 | +# config/packages/hwi_oauth.yaml |
| 140 | +hwi_oauth: |
| 141 | + connect: |
| 142 | + account_connector: App\Security\OAuthConnector |
| 143 | + registration_form: App\Form\RegistrationFormType |
| 144 | + registration_form_handler: App\Security\FormHandler |
| 145 | +``` |
| 146 | +In firewall configuration we need to change `failure_path` to the bundle route named `hwi_oauth_connect_registration`: |
| 147 | +```yaml |
| 148 | +# config/packages/security.yaml |
| 149 | +security: |
| 150 | + enable_authenticator_manager: true |
| 151 | +
|
| 152 | + firewalls: |
| 153 | + main: |
| 154 | + pattern: ^/ |
| 155 | + oauth: |
| 156 | + resource_owners: |
| 157 | + facebook: "/login/check-facebook" |
| 158 | + google: "/login/check-google" |
| 159 | + login_path: /login |
| 160 | + failure_path: hwi_oauth_connect_registration |
| 161 | +
|
| 162 | + oauth_user_provider: |
| 163 | + service: my.oauth_aware.user_provider.service |
| 164 | +``` |
| 165 | + |
| 166 | +## That was it! |
| 167 | + |
| 168 | +Now when user tries to use login functionality without having account in your application, bundle will redirect |
| 169 | +on new page where user can finish creating account. |
| 170 | + |
| 171 | +Given above examples are not production ready, and you need to adjust them to your needs. |
| 172 | + |
| 173 | +Remember that you can (and you should) also [overwrite templates provided by this bundle](https://symfony.com/doc/current/bundles/override.html#templates). |
| 174 | + |
| 175 | +## Bonus: connect existing accounts |
| 176 | + |
| 177 | +Additional functionality is allowing users to connect their existing accounts with resource owners. |
| 178 | + |
| 179 | +```jinja |
| 180 | +{% for owner in hwi_oauth_resource_owners() %} |
| 181 | + {% if attribute(app.user, owner) is empty %} |
| 182 | + <a href="{{ path('hwi_oauth_connect_service', {'service': owner}) }}">{{ owner | trans({}, 'HWIOAuthBundle') }}</a> <br /> |
| 183 | + {% else %} |
| 184 | + <span>{{ owner | trans({}, 'HWIOAuthBundle') }} connected</span> <br /> |
| 185 | + {% endif %} |
| 186 | +{% endfor %} |
| 187 | +``` |
| 188 | + |
| 189 | +[Return to the index.](index.md) |
0 commit comments