Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Oauth User Data Laravel Package

concrete5/oauth-user-data

Integrates OAuth user data into Concrete CMS, letting sites authenticate via external providers and map returned profile details to user accounts. Helps streamline registration and login by syncing names, emails, and related attributes from OAuth responses.

View on GitHub
Deep Wiki
Context7

Getting Started

What you're actually installing. concrete5/oauth-user-data is a concrete5 fork of Lusitanian's PHPoAuthUserData. It is not a Laravel package — no service provider, no facade, no config file, no migrations. It bolts a requestUserData() method onto each provider service from lusitanian/oauth and returns a normalized UserData value object (getUid(), getNickname(), getFirstName(), getLastName(), getEmail(), getLocation(), getDescription(), getImageUrl(), getUrls(), getExtra()).

What v1.0.1 changes. One thing: the Facebook extractor was updated to satisfy current Graph API requirements — in practice this means an explicit fields= parameter on the me call instead of relying on default field sets, which Facebook removed for apps created after the Graph v2.8+ migration. It is a patch release, not a redesign: no new providers, no PHP version bump, no namespace or API changes. Treat it as "Facebook un-broken as of the release date," not "package is maintained again."

First decision: if all you need is "Log in with Google/GitHub" inside a Laravel app, use laravel/socialite instead. This package only wins when you need the same user-data extraction shape across many providers, or when you're bridging a concrete5 install into a Laravel app.

Install and verify:

composer require concrete5/oauth-user-data:^1.0

Then open vendor/concrete5/oauth-user-data/composer.json before writing any code. Forks frequently rewrite Lusitanian\OAuth\... to a Concrete\... namespace to avoid collisions with the upstream package. Confirm the autoload PSR-4 prefix and the src/ layout, and confirm the lusitanian/oauth constraint (likely ~0.8). Also run git log --oneline v1.0.0..v1.0.1 against the repo if you have it — one commit touching one extractor tells you exactly how much to trust the rest.

Where to look first:

  • src/.../Service/*.php — each provider service and its requestUserData() implementation.
  • src/.../Service/Facebook.php + src/.../UserData/Extractor/Facebook.php — the only files v1.0.1 touched. Read both before wiring Facebook up; the fields list there defines what getEmail()/getImageUrl() will actually return.
  • src/.../UserData/UserData.php — the only object your app should hand to the rest of your code.
  • src/.../UserData/Extractor/* — per-provider mappers from raw JSON to UserData. Outside Facebook, this is where staleness still bites (see Gotchas).
  • src/.../Common/Http/Client/ — the injectable HTTP client you can swap for testing/logging.

Minimum viable flow (OAuth2 provider, redirect + callback):

use OAuth\Common\Consumer\Credentials;
use OAuth\Common\Storage\Session;
use OAuth\ServiceFactory;

$factory = new ServiceFactory();
$factory->setHttpClient(new \OAuth\Common\Http\Client\CurlClient());

$credentials = new Credentials(
    config('services.google.client_id'),
    config('services.google.client_secret'),
    route('oauth.callback', 'google')
);

$service = $factory->createService('google', $credentials, new Session(), ['email', 'profile']);

// redirect action
return redirect()->away($service->getAuthorizationUri()->getAbsoluteUri());

// callback action
$service->requestAccessToken(request('code'));
$userData = $service->requestUserData();

First use case: an OAuthController@redirect / @callback pair, one wildcard route per provider slug, writing $userData->getUid() and $userData->getEmail() into your users/social_accounts tables.

Implementation Patterns

Isolate the package behind one manager class. Wrap it in App\Services\OAuth\UserDataManager and bind it in a service provider. Every controller touches only your manager, never Lusitanian\ classes directly. This is the single highest-value pattern because it lets you swap to Socialite later without touching call sites.

class UserDataManager
{
    public function __construct(private array $providers) {}

    public function make(string $provider): \OAuth\Service\ServiceInterface
    {
        $cfg = $this->providers[$provider];
        return (new ServiceFactory())->createService(
            $cfg['service'], // 'google', 'facebook', ...
            new Credentials($cfg['id'], $cfg['secret'], route('oauth.callback', $provider)),
            new Session(),
            $cfg['scopes']
        );
    }

    public function redirect(string $provider): string { /* ... */ }
    public function user(string $provider, string $code): Dto { /* ... */ }
}

Config shape per provider. Store service (factory name), id, secret, scopes. Scopes are positional arrays passed to createService and are provider-specific — never build them dynamically from a shared set. Because v1.0.1 hardcodes Facebook's fields, keep Facebook's config entry separate and don't try to generalize a "fields" concept across providers; nothing else in the package reads one.

Persistence pattern. A social_accounts table is the right shape: provider, provider_uid (unique together), user_id, access_token, refresh_token, expires_at, raw JSON. Look up on (provider, provider_uid) first; fall back to email matching; otherwise create the user. Store $userData->getExtra() verbatim in the raw column so you can re-derive fields when the extractor disagrees with reality — this is what saves you the next time an extractor goes stale on you.

Token storage. The Session storage is fine for the short-lived redirect→callback dance, but use a custom OAuth\Common\Storage\TokenStorageInterface backed by your social_accounts table for anything long-lived (background profile sync, refresh). The interface is small (storeAccessToken, retrieveAccessToken, hasAccessToken, clearToken); it's the cleanest extension point in the package.

Normalize into a DTO. Don't leak UserData into your domain. Map once:

final class OAuthProfile {
    public function __construct(
        public string $provider, public string $uid, public ?string $email,
        public ?string $name, public ?string $avatar, public array $raw,
    ) {}
}

Avatar handling. Provider image URLs are often signed, sized, or short-lived (Facebook especially, and the v1.0.1 fields change means the avatar may now arrive as a nested picture.data.url shape rather than a flat picture string — check what your extractor actually emits). Either download and re-host on callback, or store the URL and accept occasional 404s; a queued "refresh avatars" job is the pragmatic middle ground.

Testing. Inject a fake OAuth\Common\Http\Client\ClientInterface via $factory->setHttpClient(...) and return canned provider JSON. Because the package does no HTTP abstraction of its own beyond that client, this is the only reliable way to test the extractors without hitting live APIs. Add a fixture for the new Facebook response shape post-v1.0.1 and a second one for the old shape — that diff is your regression test.

Queues. Never call requestUserData() inside a job — the token exchange must happen in the synchronous callback. Store tokens there, then dispatch an enrichment job that calls provider APIs with the stored token.

concrete5 interop. If you're inside concrete5, reuse its session and config services instead of $_SESSION and env(). If you're inside Laravel talking to a concrete5 DB, keep the package confined to App\Services\OAuth and treat the concrete5 side as just another data source.

Gotchas and Tips

  • One patch release does not equal maintenance. v1.0.1 is a single Facebook fix on a package whose prior release was 2017. There is still no maintainer, no dependents, no security response channel, and no PHP 8.x compatibility work. Treat everything outside the Facebook extractor as unchanged legacy. Pin PHP 7.4 or budget for patching the vendored service classes (lusitanian/oauth has known issues around deprecated signatures and dynamic properties).
  • v1.0.1 fixes fields, not the Graph version. The request path almost certainly still carries a hardcoded or absent API version. Facebook deprecates Graph versions on a rolling ~2-year clock, so the fix has a shelf life measured in months, not years. Check the URL in Service/Facebook.php; if you need to survive longer, override the endpoint constant in a subclass rather than waiting for another release.
  • Facebook fields is now load-bearing. If the hardcoded list omits email, getEmail() returns null even with the email permission granted. Read the list and, if your app needs a field it doesn't request, subclass the service and override rather than patching vendor/.
  • Dependency friction. lusitanian/oauth will conflict with league/oauth2-client (and therefore Socialite) in the same project. If you need both, they must live in separate services/packages.
  • Provider API churn remains the real killer everywhere else. These extractors still predate: Google+ shutdown, Twitter's v1.1 users/show → v2 migration (and v2's lack of email), Instagram Basic Display. Expect to override requestUserData() for every provider you use other than Facebook.
  • Email is nullable and frequently missing. Facebook only with the permission and the field; Twitter never; Google needs the email scope; GitHub hides it unless you request user:email and call the emails endpoint. Never assume getEmail() is a string.
  • Redirect URI must match byte-for-byte. Trailing-slash, http vs https, and www mismatches are the #1 cause of invalid_request / redirect_uri_mismatch. Facebook is strictest about this since their 2018 callback-URL enforcement. Build it once in config and reuse.
  • State/CSRF. Whether PHPoAuthLib sends a state parameter depends on the version and provider. Verify; if absent, add your own state to getAuthorizationUri() and validate it in the callback.
  • Scopes are provider-specific and positional. Facebook uses comma-joined scopes, Google uses space-joined, some providers ignore the fourth argument entirely. Don't share a "scopes" array across providers.
  • Debugging. Wrap the HTTP client in a logging decorator (setHttpClient(new LoggingClient($inner))) to see raw requests/responses. Log the token payload and the raw user JSON before the extractor touches it — that's how you'll diagnose every "field is suddenly null" bug, including the next Facebook schema change.
  • Error handling. Expect OAuth\Common\Http\Exception\* and OAuth\OAuth2\Service\Exception\*. Catch broadly in the callback and redirect back with a friendly message; these exceptions leak tokens and client secrets in messages, so never render them directly.
  • Refresh tokens are only exposed by providers/versions that grant them (refreshAccessToken() on the service class). Check per service; offline access usually needs an extra access_type=offline query param that PHPoAuthLib may not send for you.
  • Extension points, in order of usefulness: (1) subclass a provider service and override the endpoint constants + requestUserData() — this is now the demonstrated upgrade path, since v1.0.1 did exactly that for Facebook; (2) implement a custom TokenStorageInterface; (3) add a brand-new provider by reusing the same trait/interface and registering it with the factory.
  • License and provenance. MIT, so use is fine — but audit the vendored code and keep the wrapper thin so replacing it (likely with Socialite) is a one-class change rather than a refactor. The v1.0.1 commit is a good thing to read: it shows you the smallest viable fix pattern for when the next provider breaks.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle