Skip to content

Commit c0340bd

Browse files
authored
Merge pull request #1987 from stloyd/docs/connect
Add connect functionality docs
2 parents c68853a + 7b8fe98 commit c0340bd

File tree

3 files changed

+193
-2
lines changed

3 files changed

+193
-2
lines changed

docs/3-configuring_the_security_layer.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -113,11 +113,12 @@ github_login:
113113
114114
## That was it!
115115
116-
That's the basic setup of the bundle. If you are interested in giving users the option to "connect"
117-
social accounts check out this (todo).
116+
That's the basic setup of the bundle.
118117
119118
## Going further
120119
120+
If you would like to register user when account was not found in your application, please read [Step 4: Configuring the connect (register) layer](4-configuring_the_connect.md).
121+
121122
If you would like to define own Resource Owner (that are i.e. pretty simple or require third-party code - SDK's), or understand details of how it works under the hood, you should check [Internals](./internals) documentation.
122123
123124
[Return to the index.](index.md)

docs/4-configuring_the_connect.md

+189
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
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)

docs/index.md

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Getting Started With HWIOAuthBundle
66
1. [Setting up the bundle](1-setting_up_the_bundle.md)
77
2. [Configuring resource owners (Facebook, GitHub, Google, Windows Live) and others](2-configuring_resource_owners.md)
88
3. [Configuring the security layer](3-configuring_the_security_layer.md)
9+
4. [Configuring the connect (register) layer](4-configuring_the_connect.md)
910

1011
## Bonus:
1112

0 commit comments

Comments
 (0)