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

Oauth2 Client Laravel Package

league/oauth2-client

league/oauth2-client is a PHP library providing a base for OAuth 2.0 integrations. It includes a GenericProvider for Bearer-token services, follows RFC 6749, and can be extended for provider-specific behavior.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit

  • league/oauth2-client is a framework-agnostic protocol library, not a Laravel package. It ships no service provider, facade, config file, migration, or Blade views. In a Laravel codebase it belongs in the integration/adapter layer, wrapped by our own service classes — not called directly from controllers.
  • It implements the client side of RFC 6749 (authorization code, client credentials, refresh) as a small object model: AbstractProvider / GenericProvider, AccessToken, ResourceOwnerInterface, plus option providers and the ProviderRedirectTrait. That is a healthy seam: we can subclass per provider and keep vendor quirks out of domain code.
  • Critically for a Laravel shop: Laravel Socialite already covers the "Log in with X" use case and much of the ecosystem's provider glue (SocialiteProviders) sits on top of this library. Adopting league/oauth2-client directly is justified primarily for third-party API authorization — "connect our app to a customer's Google/Microsoft/HubSpot/Salesforce account to sync data" — and for machine-to-machine client-credentials flows. If the requirement is end-user login, the correct decision is Socialite, and this evaluation should be closed.
  • It is deliberately thin. Anything we consider product behavior (token storage, refresh scheduling, state/CSRF, PKCE lifecycle, multi-tenant credential isolation, audit logging) is our code, not the library's.

Integration feasibility

  • Low mechanical friction: composer require league/oauth2-client, PSR-4 autoloading, PSR-7 messages, Guzzle as the HTTP transport. Guzzle is already present in every modern Laravel app, so dependency weight is effectively neutral.
  • AbstractProvider::getHttpClient() accepts an injected Guzzle client, which means we can push our standard middleware (timeouts, retry, structured logging with credential redaction, tracing) into the OAuth calls rather than bolting it on.
  • The library is stateless and side-effect free with respect to persistence — it will not fight Laravel's container, queue, or cache. Binding a ProviderFactory/OAuthClientManager singleton in a dedicated service provider is a half-day of work.
  • PKCE (RFC 7636) is supported since 2.7.0 and refresh-token setting since 2.8.0 (SettableRefreshTokenInterface), which clears the two security/UX features most commonly demanded by security review.

Technical risk

  • Security surface is the dominant risk, and it is concentrated in our glue code, not the dependency: CSRF state generation/validation, PKCE verifier storage, token encryption at rest, refresh-token rotation and race conditions, and preventing client secrets or bearer tokens from reaching logs. The library gives us the primitives; it does not enforce a safe composition.
  • Provider drift: the abstraction assumes reasonably conformant providers. Non-standard error bodies throw UnexpectedValueException on non-JSON access-token responses; token expiry typing has been patched repeatedly (e.g. expires not numeric, expires_in of 0). Each provider we integrate needs its own subclass and its own tests.
  • Supply chain: the 2.8.0 release raised the Guzzle floor to 6.5.8 / 7.4.5 specifically because of known vulnerabilities in earlier versions. Any legacy service pinned to older Guzzle is a blocker until upgraded. This is a real, checkable pre-condition.
  • Static-analysis churn: 2.8.0 changed the prepareAccessTokenResponse() docblock type hint, explicitly flagged as potentially affecting downstream providers. Teams running PHPStan at high levels will see findings on minor upgrades.
  • Backwards-compatibility posture is strong: 2.x has run since 2017 with semver-respecting, mostly additive releases and per-PHP-version certification (up to PHP 8.5). The residual risk is not the library breaking; it is a future 3.0 requiring a provider-subclass migration.
  • Data-quality caveats on the supplied scoring inputs: Dependents: 0 is almost certainly a metadata artifact (this is one of the most widely depended-upon OAuth packages in PHP), and the reported last release date of 2026-09-16 is inconsistent with the newest changelog entry (2.9.0, 2025-11-25). Neither should be used to justify or reject adoption; both should be re-verified from Packagist/GitHub before this assessment is circulated.

Key questions

  1. Is the driving requirement end-user login (→ Socialite, stop here) or third-party/API authorization and M2M (→ proceed)?
  2. Which specific providers, and do official/third-party clients exist for them, or are we writing and owning subclasses?
  3. Where do tokens live — encrypted columns on a tenant table, a secrets manager, or a vault? Who owns rotation and revocation?
  4. What is the refresh strategy: on-demand with a distributed lock, or a scheduled job? What is the blast radius when a customer revokes access?
  5. Are we a confidential client (secret + PKCE) or public? What does security review require for state, PKCE, and redirect URI allowlisting?
  6. What is our Guzzle version floor today, and does it already satisfy 6.5.8 / 7.4.5?
  7. Who is the named internal owner of the provider subclasses — the code we are actually on the hook to maintain?

Integration Approach

Stack fit

  • Target Laravel 9+ / PHP 8.1+ alongside Guzzle 7.x. The library's PHP 7.1–8.5 range is broader than we need, so it will never be the constraint on a framework upgrade.
  • Place it behind a narrow internal interface, e.g. OAuthConnectionService with authorizationUrl(), exchangeCode(), refresh(), revoke(). Domain code should never type against League\OAuth2\Client\Provider\AbstractProvider.
  • Persist tokens in our own model (oauth_connections: provider, tenant/user id, encrypted access + refresh token, scopes, expires_at, revoked_at). Use Laravel's encrypted casting and never log the token attributes.
  • Use Laravel Cache::lock() keyed on the connection id around refresh to prevent thundering-herd duplicate refreshes; queue the refresh rather than doing it inline in a web request.

Migration path

  • From Socialite (for API use cases only): leave Socialite in place for login; do not attempt to consolidate. Extracting API-integration flows off Socialite and onto a purpose-built wrapper is a per-provider, per-flow migration.
  • From bespoke OAuth code: migrate one provider at a time behind the new interface. The library's GenericProvider plus a thin subclass is usually a smaller and better-tested implementation than hand-rolled cURL, so the first migration pays for itself.
  • From SocialiteProviders: note these are already league/oauth2-client-based; reuse is possible but check provider-client maintenance status before inheriting it.

Compatibility

  • Requires PSR-7 and Guzzle; already satisfied. Confirm no transitive pin holds Guzzle below 7.4.5.
  • Laravel's HTTP client is not the transport here — the package uses Guzzle directly. Configure timeouts/retries on the injected Guzzle client explicitly rather than assuming framework defaults apply.
  • Run PHPStan/Psalm on our subclasses after each minor upgrade, given the documented docblock type changes.

Sequencing

  1. Resolve the scope question (login vs. API authorization) and enumerate target providers. Gate everything on this.
  2. Spike: one provider, authorization-code + PKCE, end to end in a scratch branch. Validate error handling against a real provider failure response.
  3. Build the durable pieces: config/env schema, service provider bindings, encrypted token model + migration, state/PKCE handling, redirect route + controller.
  4. Harden: refresh with locking and queueing, revocation, admin "disconnect" path, structured logs with redaction, tests including expired/revoked/invalid-scope cases.
  5. Roll out remaining providers against the established pattern; add a provider checklist to the runbook.
  6. Wire monitoring and an on-call runbook before the first production customer connects a live account.

Operational Impact

Maintenance

  • The dependency itself is near-zero maintenance: a stable, narrow surface, MIT-licensed, vendor-maintained since 2013, with a strong semver record and ongoing PHP-version certification. Expect one or two minor bumps per year.
  • Our wrappers and provider subclasses are the real maintenance load. Budget for a named owner, a dependency-update cadence, and a per-provider regression test because providers change their endpoints and error formats unilaterally.
  • Track Guzzle security advisories; the project has historically responded promptly and raised its own floor when needed.

Support

  • Community-supported only: GitHub issues, Gitter, documentation site. No SLA, no vendor escalation path. If the upstream project stalls on a critical issue, our options are a patch via a Composer fork or an internal workaround.
  • Because the library is small and readable, that risk is manageable — but it must be an explicit, accepted risk rather than an unexamined assumption.

Scaling

  • The library adds no meaningful runtime overhead and is stateless; scaling is governed entirely by our design:
    • Token store: read-heavy access-token lookups → cache with a TTL safely inside expires_in, minus a skew buffer.
    • Refresh contention: a popular connection refreshed concurrently by many workers will burn a rotating refresh token. Locking plus a single owning queue is mandatory, not an optimization.
    • Provider rate limits: outbound call volume, not PHP, becomes the ceiling. Centralize the Guzzle client so backoff and metrics are uniform.
    • Multi-tenant fan-out: N customers × M providers means N×M credentials and error states; plan the disconnect/reauth UX at design time.

Failure modes

  • Refresh token revoked or expired: silent breakage of a customer integration days later. Detect provider 400/401 on refresh, mark the connection needs_reauth, and surface it in-app and via alert.
  • Non-JSON or HTML error page from a provider: library throws UnexpectedValueException; unwrapped, this produces 500s in the callback route. Catch and translate to a user-facing "authorization failed, try again."
  • state mismatch or expired state: users returning from a slow provider login. Decide the UX (restart flow vs. error) up front; store state with a short TTL.
  • Clock skew / non-numeric expires_in: historically a source of bugs; treat unknown expiry conservatively (assume short TTL, refresh early).
  • Redirect URI mismatch, scope not granted, or provider outage: distinguish configuration faults (our bug, fixable) from provider faults (retry) in the error taxonomy — otherwise on-call cannot triage.
  • Secret and token leakage through logs: the highest-severity failure and entirely within our control. Redact in Guzzle middleware, not in ad-hoc log statements.
  • Uncontrolled concurrency on refresh producing invalidated-token loops — the classic OAuth operational incident; see locking above.

Ramp-up

  • Low for a mid-level Laravel engineer: the usable API is three or four classes, and the docs plus the basic usage guide suffice. The learning curve is OAuth 2.0 itself — authorization code flow, PKCE, state, refresh semantics, scope negotiation — not the package.
  • Realistic estimate: 1–2 days to a working single-provider spike; 1–2 engineering weeks to a production-hardened, tested, observable implementation covering 2–3 providers including token storage, refresh, revocation, and the admin disconnect path.
  • Recommend a shared internal note documenting each provider's subclass quirks. That document, not the package docs, will be what the next engineer actually needs.
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.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow