beshkenadze/oauth-bundle
Symfony2 bundle for OAuth user authentication (fork of KnpOAuthBundle). Adds OAuth login support for multiple providers, with documentation in Resources/doc and MIT license.
Installation:
composer require beshkenadze/oauth-bundle
Enable the bundle in config/bundles.php:
return [
// ...
HWI\OAuthBundle\HWIOAuthBundle::class => ['all' => true],
];
Configure Providers:
Edit config/packages/hwi_oauth.yaml (or create it) to define supported OAuth providers (e.g., Google, Facebook):
hwi_oauth:
connect:
account_connector: your_app.connector.my_oauth
# List of supported providers
providers:
google:
type: google
client_id: "YOUR_CLIENT_ID"
client_secret: "YOUR_CLIENT_SECRET"
scope: "email profile"
facebook:
type: facebook
client_id: "YOUR_APP_ID"
client_secret: "YOUR_APP_SECRET"
First Use Case:
Add OAuth login buttons to a template (e.g., templates/security/login.html.twig):
<a href="{{ path('hwi_oauth_connect', {'service': 'google'}) }}" class="btn btn-google">
Login with Google
</a>
<a href="{{ path('hwi_oauth_connect', {'service': 'facebook'}) }}" class="btn btn-facebook">
Login with Facebook
</a>
Verify Routes:
Check php bin/console debug:router | grep hwi_oauth to confirm routes like:
hwi_oauth_connect_<provider> (e.g., hwi_oauth_connect_google)hwi_oauth_check_<provider>hwi_oauth_connectUser Authentication Flow:
hwi_oauth_connect_<provider>.hwi_oauth_check_<provider> with an authorization code.account_connector (see below) maps provider data to your user entity.Account Connector:
Implement a service to link OAuth data to your user model (e.g., src/Service/MyOAuthConnector):
namespace App\Service;
use HWI\Bundle\OAuthBundle\OAuth\Response\UserResponseInterface;
use HWI\Bundle\OAuthBundle\Security\Core\User\OAuthUserProvider;
use Symfony\Component\Security\Core\User\UserInterface;
class MyOAuthConnector implements AccountConnectorInterface
{
public function connect(UserResponseInterface $response, UserInterface $user = null)
{
// Example: Update user's email/name from $response->getEmail()
$user->setEmail($response->getEmail());
$user->setFirstName($response->getFirstName());
$user->addRole('ROLE_OAUTH');
return $user;
}
}
Register it in services.yaml:
services:
your_app.connector.my_oauth:
class: App\Service\MyOAuthConnector
tags:
- { name: hwi_oauth.account_connector, connector: account_connector }
Post-Auth Logic:
Use Symfony’s security.event.authentication_success event to trigger actions after OAuth login:
// src/EventListener/OAuthLoginListener.php
public function onAuthenticationSuccess(AuthenticationEvents $events)
{
$event = $events->getAuthenticationEvent();
if ($event->getAuthenticationToken()->getUser() instanceof OAuthUser) {
// Custom logic (e.g., log activity, redirect)
}
}
Register the listener in services.yaml:
services:
App\EventListener\OAuthLoginListener:
tags:
- { name: kernel.event_listener, event: security.authentication.success, method: onAuthenticationSuccess }
Custom User Provider:
Extend OAuthUserProvider to fetch users from your database:
use HWI\Bundle\OAuthBundle\Security\Core\User\OAuthUserProvider as BaseOAuthUserProvider;
class CustomOAuthUserProvider extends BaseOAuthUserProvider
{
public function loadUserByOAuthUserResponse(UserResponseInterface $response)
{
$username = $response->getEmail();
$user = $this->userManager->findUserBy(['email' => $username]);
if (!$user) {
$user = $this->userManager->createUser([
'email' => $username,
'roles' => ['ROLE_USER'],
]);
}
return $user;
}
}
Configure in security.yaml:
security:
providers:
oauth_user_provider:
id: App\Security\CustomOAuthUserProvider
Provider-Specific Scopes: Dynamically set scopes per provider in the connector:
public function connect(UserResponseInterface $response, UserInterface $user = null)
{
if ($response->getProviderName() === 'google') {
$user->addRole('ROLE_GOOGLE_USER');
}
return $user;
}
CSRF Protection:
Disable CSRF for OAuth routes if using tokens (e.g., in config/packages/security.yaml):
security:
access_control:
- { path: ^/connect/, roles: PUBLIC_ACCESS }
Testing:
Use HWI\Bundle\OAuthBundle\Tests\Mock\MockHttpFoundation to simulate OAuth responses in PHPUnit:
$mockResponse = new MockHttpFoundation($this->getMockResponse());
$this->client->getContainer()->set('request', $mockResponse);
Token Expiry:
HWI\Bundle\OAuthBundle\Exception\TokenExpiredException in your connector:
try {
$response = $this->oauth->performStep();
} catch (TokenExpiredException $e) {
// Refresh token or re-authenticate
return $this->redirectToAuthPage();
}
Missing User Fields:
hwi_oauth.yaml:
scope: "user:email,read:org"
Circular Redirects:
hwi_oauth_connect routes are accessible anonymously:
security:
access_control:
- { path: ^/login, roles: PUBLIC_ACCESS }
- { path: ^/connect, roles: PUBLIC_ACCESS }
Database Locks:
$entityManager->beginTransaction();
try {
$user = $entityManager->getRepository(User::class)->findOneBy(['email' => $email]);
if (!$user) {
$user = new User();
$user->setEmail($email);
$entityManager->persist($user);
}
$entityManager->commit();
} catch (\Exception $e) {
$entityManager->rollback();
throw $e;
}
Provider-Specific Quirks:
openid scope for basic profile data.null for optional fields (e.g., phone). Handle gracefully:
$phone = $response->getPhone() ?: 'N/A';
Enable Debugging:
Set debug: true in hwi_oauth.yaml to log OAuth responses:
hwi_oauth:
debug: true
Check logs at var/log/dev.log for raw provider responses.
Common Errors:
InvalidArgumentException: Missing client_id/client_secret in config.RuntimeException: Provider type not registered (e.g., type: google requires hwi_oauth.resource_owners.google).ClientException: Invalid redirect URI. Ensure it matches the provider’s registered callback URL.Provider-Specific Debugging:
HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\GenericResourceOwner:
namespace App\OAuth;
use HWI\Bundle\OAuthBundle\OAuth\ResourceOwner\GenericResourceOwner;
class CustomProvider extends GenericResourceOwner
{
How can I help you explore Laravel packages today?