symfonycorp/connect
PHP SDK and Symfony bundle for integrating Symfony Connect, the OAuth2 service for Symfony users. It handles authentication, lets users sign in with their Symfony Connect account, and provides API access for retrieving profile and account data.
symfonycorp/connect is a thin, framework-agnostic SDK for the SymfonyConnect API. It does two things: drive the OAuth2-style authorization-code flow against connect.symfony.com, and give you an authenticated client for the Connect REST API (users, badges, projects). It is not a Symfony bundle — there is no recipe, no Extension, no auto-wiring. Expect to spend most of your time in your own security.yaml / services.yaml, not in this library.
Minimal steps:
composer require symfonycorp/connect
.env.local:SYMFONY_CONNECT_CLIENT_ID=abc123
SYMFONY_CONNECT_CLIENT_SECRET=shhh
SYMFONY_CONNECT_REDIRECT_URI=https://example.com/connect/callback
# config/services.yaml
services:
SymfonyCorp\Connect\OAuth\OAuth:
arguments:
$clientId: '%env(SYMFONY_CONNECT_CLIENT_ID)%'
$clientSecret: '%env(SYMFONY_CONNECT_CLIENT_SECRET)%'
calls:
- [setRedirectUrl, ['%env(SYMFONY_CONNECT_REDIRECT_URI)%']]
Where to look first, in this order:
README.md — the only end-to-end example, and it is short.src/OAuth/OAuth.php — getAuthorizationUrl() and getAccessToken($code). This is the whole login flow.src/Api/Api.php and src/Api/Entity/ — the authenticated client and its read-only DTOs (User, Badge, …).src/Security/ — legacy Symfony Security glue. Read it to understand the mapping, do not use it on Symfony 5.4+ (see Gotchas).First use case — a "Log in with SymfonyConnect" button, controller-only, no Security bundle integration:
use SymfonyCorp\Connect\OAuth\OAuth;
use SymfonyCorp\Connect\Api\Api;
#[Route('/connect')]
final class ConnectController extends AbstractController
{
#[Route('/start', name: 'connect_start')]
public function start(OAuth $oauth): Response
{
return $this->redirect($oauth->getAuthorizationUrl());
}
#[Route('/callback', name: 'connect_callback')]
public function callback(Request $request, OAuth $oauth): Response
{
$token = $oauth->getAccessToken($request->query->get('code'));
$api = new Api($token);
$me = $api->getRoot()->getCurrentUser();
// $me->getUsername(), $me->getEmail() -> persist to YOUR User entity
return $this->redirectToRoute('dashboard');
}
}
Method names drift between minor versions — if a call above does not exist in your pinned tag, grep the class; the surface area is small enough to read in one sitting.
Map the Connect user to a local user on first login. Never treat the SDK's User DTO as your application user — it is a detached value object, not a Doctrine entity. Store connect_id (and optionally the access token) on your own User row:
$user = $this->userRepository->findOneByConnectId($me->getId())
?? (new User())->setConnectId($me->getId());
$user->setUsername($me->getUsername())
->setEmail($me->getEmail())
->setConnectToken($token); // encrypt at rest if you store it
$this->em->persist($user);
$this->em->flush();
Integrate with Symfony Security via a custom authenticator, not via the bundled ConnectUserProvider / ConnectAuthenticationProvider. On Symfony 5.4+/6/7 subclass AbstractAuthenticator and treat the whole OAuth round-trip as the authenticate() step, or (cleaner) keep the controller above and just call $security->login($user, 'form_login') after the mapping. The latter keeps the OAuth controller testable and avoids fighting the authenticator lifecycle.
Store the token only if you need the API later. If you only log users in, discard it. If you want to display a user's badges or projects, persist it and build a scoped service:
final class ConnectApiFactory
{
public function forUser(User $user): Api
{
return new Api($user->getConnectToken());
}
}
Cache aggressively. API responses are public-ish and change slowly; wrap calls in a cache.app pool keyed by Connect ID with a 15–60 minute TTL, and refresh on demand rather than on every page render.
Test against a mocked HTTP layer. The SDK funnels requests through its HTTP client dependency, so MockHttpClient (or your PSR-18 mock) lets you assert the token exchange and the /me call without touching the network. Assert on the authorization URL separately — it is pure string building.
Wrap it. Given the low release cadence, put the SDK behind a one-method interface (ConnectAuthenticator::authenticate(string $code): ConnectIdentity) so an upstream breakage or a switch to plain league/oauth2-client is a single-file change.
services.yaml, by hand. Budget an hour for the first wiring.SymfonyCorp\ namespace is not Symfony\. It is a separate vendor namespace; do not assume it ships with the framework or that the classes are covered by Symfony's BC promise.src/Security/ classes are version-locked. The ConnectUserProvider/ConnectToken code was written for the old Guard era. On modern Symfony they at best work by accident; use AbstractAuthenticator or post-controller login instead.getAccessToken() — catch it and redirect back to /connect/start rather than 500-ing.state is your job. The authorization URL is a plain string; if your version does not accept a state parameter, append one and verify it in the callback against the session. Without it the callback endpoint is a login-CSRF vector.getEmail() returns null, which will blow up a non-nullable setEmail(). Guard every DTO getter, or validate before mapping.getAccessToken() and getRoot(), log the exception message plus the HTTP status, and — in dev only — dump the raw token-endpoint response; that is where invalid_client / invalid_grant hides.final-ish in spirit."symfonycorp/connect": "^2.0") and watch the repo; 90-odd stars and a small dependent count mean upstream fixes are infrequent, so you own the risk.How can I help you explore Laravel packages today?