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 Laravel Package

lusitanian/oauth

A PHP library for OAuth 1 and OAuth 2 authentication, offering ready-made service wrappers for dozens of providers like Google, Facebook, Twitter, and GitHub. It handles token requests, refreshes, and API calls, and integrates with Laravel via a service provider and facade.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and understand the four moving parts. composer require lusitanian/oauth:^0.8.11 gives you a library with no Laravel glue. Everything hangs off four objects you will wire yourself:

  • OAuth\Common\Consumer\Credentials — client id, secret, and callback URL.
  • OAuth\Common\Storage\TokenStorageInterface — where access/request tokens and OAuth2 state live (session, cache, database).
  • OAuth\Common\Http\Client\ClientInterface — the HTTP transport (CurlClient by default, StreamClient if you lack ext-curl).
  • OAuth\ServiceFactory — maps a provider name ('Google', 'GitHub', 'Twitter') to a service class.

Look first at: src/OAuth/ServiceFactory.php (supported provider names and createService() signature), src/OAuth/OAuth2/Service/AbstractService.php (getAuthorizationUri, requestAccessToken, refreshAccessToken, request), src/OAuth/Common/Storage/Session.php, and the README examples for Facebook/Twitter. The provider classes themselves (src/OAuth/OAuth2/Service/*.php) are the authoritative list of scopes and endpoints.

Note the 0.8.11 declaration fix before you wire anything. The current release corrects the declaration of AbstractService::requestAccessToken() — i.e. its signature, not its behaviour. Read the method head in AbstractService.php on the tag you install, and diff v0.8.10...v0.8.11, before you (a) subclass a provider service, (b) call requestAccessToken() with named arguments, or (c) copy a snippet from an older tutorial. Everything below assumes you are on ^0.8.11 and pin it.

First use case: a single-provider "connect GitHub" spike. Start in a controller, using a storage adapter that does not touch PHP's native session (see below), because the bundled Session storage calls session_start() and will fight Laravel's session handling.

// app/Providers/OAuthServiceProvider.php
$this->app->singleton(\OAuth\ServiceFactory::class, fn () => new \OAuth\ServiceFactory(new \OAuth\Common\Http\Client\CurlClient()));
// routes/web.php
Route::get('/auth/{provider}', [OAuthController::class, 'redirect']);
Route::get('/auth/{provider}/callback', [OAuthController::class, 'callback']);
// app/Http/Controllers/OAuthController.php
public function redirect(string $provider)
{
    $service = $this->makeService($provider);
    $state = Str::random(40);
    session(["oauth.state.$provider" => $state]);

    return redirect()->away($service->getAuthorizationUri(['state' => $state]));
}

public function callback(string $provider)
{
    abort_unless(hash_equals(session()->pull("oauth.state.$provider"), request('state')), 403);

    $service = $this->makeService($provider);
    $token = $service->requestAccessToken(request('code'));
    $profile = json_decode($service->request('/user'), true);

    // persist $profile, associate with auth()->user(), etc.
    return redirect('/dashboard');
}

private function makeService(string $provider)
{
    $cfg = config("services.$provider");
    $credentials = new \OAuth\Common\Consumer\Credentials(
        $cfg['client_id'],
        $cfg['client_secret'],
        $cfg['redirect']
    );

    return app(\OAuth\ServiceFactory::class)->createService(
        $cfg['service'],           // exact provider class name, e.g. 'GitHub'
        $credentials,
        app(TokenStorage::class),
        $cfg['scopes'] ?? []
    );
}
// config/services.php
'github' => [
    'service' => 'GitHub',
    'client_id' => env('GITHUB_CLIENT_ID'),
    'client_secret' => env('GITHUB_CLIENT_SECRET'),
    'redirect' => env('APP_URL').'/auth/github/callback',
    'scopes' => ['user:email'],
],

Implementation Patterns

Custom TokenStorageInterface backed by Laravel session (or cache/DB). This is the first thing to build on every project; it removes the native-session conflict and lets you swap to Redis or a table for queue/CLI flows. The interface keys everything by service name (retrieveAccessToken($service), storeAccessToken($service, TokenInterface $token), hasAccessToken, clearToken, plus the authorization-state methods), so one instance can serve many providers.

class LaravelSessionTokenStorage implements \OAuth\Common\Storage\TokenStorageInterface
{
    public function retrieveAccessToken($service) { return session("oauth.$service.token"); }
    public function storeAccessToken($service, \OAuth\Common\Token\TokenInterface $token) { session(["oauth.$service.token" => $token]); }
    public function hasAccessToken($service) { return session()->has("oauth.$service.token"); }
    public function clearToken($service) { session()->forget("oauth.$service.token"); }
    public function clearAllTokens() { /* iterate keys */ }
    // retrieve/store/has/clear AuthorizationState map to oauth.$service.state
}

For OAuth1 (Twitter), the request token secret must survive the redirect; use the same storage's authorization-state slots rather than a random session key, or you will lose the verifier handshake.

Provider-agnostic service builder. Keep one makeService() method (as above) driven by config/services.php. Provider name strings are used both as factory lookup keys and as storage keys, so centralize them — mixing 'google' and 'Google' will produce "no token" bugs that look like auth failures.

Guzzle HTTP client for observability. CurlClient gives you no logging, retries, or timeouts. Implement OAuth\Common\Http\Client\ClientInterface::retrieveResponse(UriInterface $endpoint, $method, array $params = [], array $extraHeaders = []) over Guzzle: GET params as query, everything else as http_build_query, return the raw string body, and throw on non-2xx so the library's token exceptions surface normally.

Refresh before you call. The service stores the token but does not proactively refresh for you. In a middleware or a withToken() helper:

$token = $storage->retrieveAccessToken('Google');
if ($token->isExpired()) {
    $token = $service->refreshAccessToken($token);
    $storage->storeAccessToken('Google', $token);
}

Custom providers by extension. When a provider isn't in ServiceFactory, extend OAuth\OAuth2\Service\AbstractService, set $authorizationEndpoint, $accessTokenEndpoint, $scopes, $scopesSeparator, override getAuthorizationMethod() (bearer header vs query string), and instantiate it directly with the same constructor arguments. If you also override requestAccessToken() — a common way to add token-response logging or to normalise a non-standard grant — the override must be signature-compatible with the declaration shipped in v0.8.11, otherwise PHP fatals at class-load time. This is also the escape hatch for self-hosted OAuth servers and enterprise tenants with custom endpoints.

Testing with Memory storage and a fake client. OAuth\Common\Storage\Memory plus a stub ClientInterface returning canned JSON lets you test controllers and token-refresh logic without network calls or a real browser. Seed it with OAuth\OAuth2\Token\StdOAuth2Token('fake-token', 3600).

Where this beats Socialite: providers Socialite has dropped or never supported, OAuth1 flows with non-Twitter providers, and services where you need to control the raw authorization URI and scopes. For standard Google/GitHub/Facebook logins, Socialite is less code and is maintained.

Gotchas and Tips

  • Low-maintenance, not unmaintained — but barely. v0.8.11 (a one-line declaration fix) is the first release in years; treat the library as stable-but-dormant rather than dead. Still no Laravel integration, still expect PHP 8.x deprecation noise (dynamic properties, optional-parameter ordering) in the untouched files, and still budget time to fork or suppress. Pin ^0.8.11 and run the full auth flow on your target PHP version before committing.
  • The 0.8.11 signature fix can break subclasses. "Fixed declaration of requestAccessToken" means the method head changed. Three concrete risks: (1) a custom service that overrides requestAccessToken() with the old signature now throws a fatal "declaration must be compatible" error; (2) callers using named arguments break if any parameter was renamed; (3) code copied from pre-0.8.11 tutorials/StackOverflow may no longer type-check. Grep your codebase for requestAccessToken and re-run your auth tests after upgrading.
  • Native session trap. OAuth\Common\Storage\Session calls PHP session functions. Under Laravel, tokens written there are invisible to the framework and can break stateless APIs/Octane. Default to a custom storage adapter.
  • State is not automatic. The library does not generate or verify OAuth2 state. Add it to getAuthorizationUri(['state' => ...]) and verify it in the callback, or you ship a CSRF hole. OAuth1's protection is the request-token secret — persist it across the redirect.
  • Redirect URI is exact-match. http vs https, trailing slash, APP_URL in local vs production, and the callback route must be registered outside auth middleware. Mismatches produce opaque provider errors, not library errors.
  • Scopes are fixed at service creation. Passing different scopes later won't re-authorize an existing token; users must reconnect. Scopes separators differ ( vs ,) — let the provider class handle it, don't pre-join.
  • Refresh-token nuances. Google returns a refresh token only on the first consent unless you pass access_type=offline (and often prompt=consent) as additional authorization parameters. Store the refresh token separately; isExpired() returns null-safe results when expires_in is absent, so treat "no expiry" as long-lived, not broken.
  • Error surfaces are thin. Token endpoint failures throw OAuth\Common\Http\Exception\TokenResponseException with the provider's body attached — log that body. API request() failures come back as raw non-2xx responses depending on your HTTP client, so wrap calls in your own exception layer.
  • POST bodies and content types. request($path, 'POST', $body, $headers) sends form-encoded data by default. JSON APIs need Content-Type: application/json plus json_encode, and some providers reject Bearer headers in favor of query-string access tokens (check getAuthorizationMethod()).
  • Web vs queue token access. Session-backed tokens vanish for queue workers and scheduled commands. If background jobs call the provider, move storage to the database or cache, encrypt the token column (encrypted cast), and never log tokens or client secrets.
  • Extension points worth knowing: your own ClientInterface (retries/timeouts/logging), your own TokenStorageInterface (multi-tenant tokens keyed by user + service), subclassing ServiceFactory to register custom providers, and AbstractService endpoint properties for non-standard hosts. When you use any of these, add a test that instantiates the subclass — that's what catches a future signature change like 0.8.11's.
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.
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource