Skip to content

Instantly share code, notes, and snippets.

@mark-gerarts
Last active June 21, 2024 12:09
Show Gist options
  • Save mark-gerarts/70397d098805a150a64e3d0f2b6b898b to your computer and use it in GitHub Desktop.
Save mark-gerarts/70397d098805a150a64e3d0f2b6b898b to your computer and use it in GitHub Desktop.
HWIOAuthBundle connect functionality without FUB

HWIOAuthBundle Connect

This describes the "connect" functionality that is currently undocumented in the HWIOAuthBundle. This allows you to connect a user to a resource owner when that user is already logged in using some other method (without FOSUserBundle).

Note: you have to use version 1.2 for now, see #1007.

This was mostly figured out by reading hwi/oauth-bundle/Controller/ConnectController.php.

hwi_oauth:
# list of names of the firewalls in which this bundle is active, this setting MUST be set
firewall_names:
- main
# https://github.com/hwi/HWIOAuthBundle/blob/master/Resources/doc/2-configuring_resource_owners.md
resource_owners:
google:
type: google
client_id: '%env(GOOGLE_OAUTH_CLIENT_ID)%'
client_secret: '%env(GOOGLE_OAUTH_CLIENT_SECRET)%'
scope: email
# For the full reference:
# https://github.com/hwi/HWIOAuthBundle/blob/master/Resources/doc/internals/reference_configuration.md
#
# We don't use the registration form in this example, we assume you already have a way to log
# in users using e.g. username/password.
connect:
# If enabled, the user has to confirm after logging in wit the third party. The template can
# be customized at @HWIOAuth/Connect/connect_confirm.html.twig
confirmation: false
# A service that implements HWI\Bundle\OAuthBundle\Connect\AccountConnectorInterface, in
# this example simply the UserProvider.
account_connector: Acme\Security\Provider\UserProvider
<?php
namespace Acme\Controller;
use HWI\Bundle\OAuthBundle\Templating\Helper\OAuthHelper;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Http\Util\TargetPathTrait;
// This is completely optional. It simply allows for a prettier URL,
// and makes HWI redirect to the path I want, instead of showing a
// success message.
final class OAuthConnectController extends AbstractController
{
use TargetPathTrait;
private OAuthHelper $oAuthHelper;
// This service isn't public by default, alias it in your services.yaml:
// HWI\Bundle\OAuthBundle\Templating\Helper\OAuthHelper: '@hwi_oauth.security.oauth_utils'
public function __construct(OAuthHelper $oAuthHelper)
{
$this->oAuthHelper = $oAuthHelper;
}
public function __invoke(Request $request, string $type): Response
{
$redirectUrl = $this->generateUrl(
'hwi_oauth_connect_service',
['service' => $type],
UrlGeneratorInterface::ABSOLUTE_URL
);
$connectUrl = $this->oAuthHelper->getAuthorizationUrl(
$type,
$redirectUrl
);
// We save the user settings page as a target path because the HWI
// ConnectController shows a confirmation page if this isn't set.
$this->saveTargetPath(
$request->getSession(),
'main',
$this->generateUrl('my_custom_url')
);
return $this->redirect($connectUrl);
}
}
hwi_oauth_redirect:
resource: "@HWIOAuthBundle/Resources/config/routing/redirect.xml"
prefix: /connect
hwi_oauth_connect:
resource: "@HWIOAuthBundle/Resources/config/routing/connect.xml"
prefix: /connect
google_login:
path: /login/check-google
# Optional custom route with a prettier url
my_app.oauth_connect:
path: /sso/{type}/connect
methods: [GET]
controller: Acme\Controller\OAuthConnectController
requirements:
type: 'google|office365'
<?php
namespace Acme\Security\Provider;
use HWI\Bundle\OAuthBundle\Connect\AccountConnectorInterface;
use HWI\Bundle\OAuthBundle\OAuth\Response\UserResponseInterface;
use HWI\Bundle\OAuthBundle\Security\Core\User\OAuthAwareUserProviderInterface;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Acme\Domain\Model\Account;
use Acme\Domain\Repository\AccountRepositoryInterface;
// We implement the AccountConnectorInterface on our user provider, but
// you can define a separate service as well.
final class UserProvider implements
UserProviderInterface,
OAuthAwareUserProviderInterface,
AccountConnectorInterface
{
private AccountRepositoryInterface $accountRepository;
public function __construct(AccountRepositoryInterface $accountRepository)
{
$this->accountRepository = $accountRepository;
}
/** user provider methods are ommitted **/
// This method is used when logging in, and is already documented in the bundle itself.
public function loadUserByOAuthUserResponse(UserResponseInterface $response): UserInterface
{
$account = $this->accountRepository->findOneByResourceOwnerAndIdentifier(
$response->getResourceOwner()->getName(),
$response->getData()['id']
);
if ($account === null) {
throw new UsernameNotFoundException('No account associated');
}
return $account;
}
// This method is called when the connect is succesful. You should store
// the association in the database here.
public function connect(
UserInterface $user,
UserResponseInterface $response
) {
if (!$user instanceof Account) {
throw new \Exception('Invalid user');
}
// How you store the association is application specific,
// this is just an example
$account->addConnection(
$response->getResourceOwner()->getName(),
$response->getData()['id']
);
$this->accountRepository->persist($account);
}
}
{#
If you want to put the link directly in your template, use the following.
It creates an URL to authenticate with your provider (google in this case),
with the redirect_url set to HWI's connect controller route.
#}
<a href="{{ hwi_oauth_authorization_url('google', url('hwi_oauth_connect_service', {service: google})) }}">
Connect with Google
</a>
{# I use a custom controller instead, for a cleaner URL #}
<a href="{{ path('my_app.oauth_connect', {type: 'google'}) }}">
Connect with Google
</a>
@mark-gerarts
Copy link
Author

Thanks! I updated the gist.

@lxregistry
Copy link

Error: Class App\Security\UserProvider contains 3 abstract methods and must therefore be declared abstract or implement the remaining methods (Symfony\Component\Security\Core\User\UserProviderInterface::refreshUser, Symfony\Component\Security\Core\User\UserProviderInterface::supportsClass, Symfony\Component\Security\Core\User\UserProviderInterface::loadUserByIdentifier)

@lxregistry
Copy link

Cannot autowire service "App\Controller\OAuthConnectController": argument "$oAuthHelper" of method "__construct()" has type "HWI\Bundle\OAuthBundle\Templating\Helper\OAuthHelper" but this class was not found.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment