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

Laravel Oauth2 Laravel Package

madewithlove/laravel-oauth2

An OAuth 2.0 authorization and resource server for Laravel, built on league/oauth2-server. It ships ready-made grant types, token repositories, middleware, and configuration to secure your API with access tokens, refresh tokens, and scopes.

View on GitHub
Deep Wiki
Context7

Getting Started

The package is a thin Laravel wrapper around league/oauth2-server, so the first hour is mostly about getting the wrapper registered and the four OAuth tables seeded. Verify all class/config names against the published files — this predates modern package conventions and the README is thin.

  1. Install (Laravel 4.x / early 5.x era constraints — see Gotchas):

    composer require madewithlove/laravel-oauth2
    
  2. Register the provider and aliases in the places your Laravel version expects (app/config/app.php on L4, config/app.php on L5):

    'providers' => [
        'Madewithlove\LaravelOauth2\OAuth2ServiceProvider',
    ],
    'aliases' => [
        'OAuth2Server'   => 'Madewithlove\LaravelOauth2\Facades\OAuth2Server',
        'ResourceServer' => 'Madewithlove\LaravelOauth2\Facades\ResourceServer',
    ],
    
  3. Publish config + migrations, then migrate:

    php artisan config:publish madewithlove/laravel-oauth2   # L4
    php artisan vendor:publish --provider="Madewithlove\LaravelOauth2\OAuth2ServiceProvider"
    php artisan migrate
    
  4. Read these three things first, in order:

    • The published oauth2.php config — it defines which grants are enabled and how clients/tokens are stored.
    • The published migrations — they are the contract for the clients, access_tokens, refresh_tokens, and scopes tables.
    • The service provider — it shows exactly which container keys and repository bindings you can override.
  5. First use case — protect one existing API route, not the whole app:

    Route::group(['prefix' => 'api', 'before' => 'oauth2'], function () {
        Route::get('me', function () { return Auth::user(); });
    });
    
  6. Get a token in two commands so you know the machinery works before writing any grant code. Insert a client row manually (see Gotchas on column formats), then:

    curl -X POST http://localhost/oauth/access-token \
      -d grant_type=client_credentials \
      -d client_id=1 -d client_secret=secret
    

    You should get back access_token, token_type, expires_in. If you do, the rest is configuration.

Implementation Patterns

One token endpoint, all grants. Route a single POST handler to the authorization server facade and let the grant_type parameter dispatch:

Route::post('oauth/access-token', function () {
    return OAuth2Server::issueAccessToken();
});

This covers authorization code, password, client credentials, and refresh token simultaneously — the enabled grant list lives on the client row and in config, not on the route. Resist creating /oauth/token/password style routes; you'll duplicate validation and break refresh flows.

Scope enforcement as middleware parameters. Rather than checking scopes inside controllers, push it down onto the route:

Route::group(['before' => 'oauth2.scope:read'], function () {
    Route::get('api/orders', 'Api\OrdersController@index');
});

Route::group(['before' => 'oauth2.scope:read,write'], function () {
    Route::post('api/orders', 'Api\OrdersController@store');
});

The middleware does the 403 insufficient_scope response for you; controllers stay clean and testable.

Password grant = custom user resolution. The password grant needs to look up an owner by username and verify the password. The package delegates this. Hook it via the config callable or by implementing/binding the owner repository interface:

// config/oauth2.php (shape depends on version — confirm against source)
'password_grant' => [
    'callback' => function ($username, $password) {
        return Auth::validate(['email' => $username, 'password' => $password])
            ? User::where('email', $username)->first()
            : null;
    },
],

Keep this delegate thin — no side effects, no session writes. It's called on every token request and must be side-effect free or you'll get duplicate login events.

Custom storage = bind the interface, don't edit the package. Every repository (clients, access tokens, refresh tokens, scopes) is a container binding. For a multi-tenant setup, implement the repository interface for your tenant-aware model and rebind:

$this->app->bind(
    'Madewithlove\LaravelOauth2\Repositories\ClientRepositoryInterface',
    'App\OAuth\TenantClientRepository'
);

This is the main extension point and the only safe way to survive a package upgrade.

Token issuance from your own controllers. For first-party clients (mobile app, SPA) you often skip the HTTP round-trip and issue directly, returning the same JSON shape the endpoint would:

$token = OAuth2Server::issueAccessToken();
return Response::json($token)->header('Cache-Control', 'no-store');

Mirror the endpoint's headers exactly — clients written against RFC 6749 will expect them.

Refresh-token rotation on logout. There's no automatic revocation. On logout, explicitly expire the current access token and its refresh token:

OAuth2Server::getAccessToken()->expire();
OAuth2Server::getRefreshToken()->expire();

Third-party clients via authorization code. Seed client rows with a redirect_uri and the authorization_code grant, send users to your /oauth/authorize route, and validate redirect_uri against the stored value before redirecting — the league library does some of this, but confirm it, because a loose redirect check is the classic OAuth hole.

Gotchas and Tips

  • Accept that it's archived. The last release was 2014 and the repo is archived. On anything past PHP 7.2 / Laravel 5.4, installation is likely to fail on composer install or on runtime signature mismatches with league/oauth2-server. Budget a migration to laravel/passport or a direct league/oauth2-server integration before adopting this for new work.
  • Grants are per-client and global. A grant must be enabled in config and listed on the client record. "unsupported_grant_type" almost always means you forgot one of the two — don't debug the token endpoint until you've dumped the client row.
  • Scopes must pre-exist. Requesting a scope that isn't in the scopes table returns invalid_scope. Seed your scopes in a migration or seeder, and keep that list in version control — it's effectively part of your API contract.
  • Client rows aren't user-friendly to hand-edit. Fields like redirect_uri, grant_types, and scopes are stored in formats (serialized/JSON, depending on version) that a raw SQL insert will get wrong. Always create clients through Eloquent models or a seeder, never INSERT by hand.
  • Header hygiene on token responses. RFC 6749 requires Cache-Control: no-store and Pragma: no-cache on token responses. The library adds these, but if you return tokens from your own controller (see above), you lose them — and proxies will happily cache a bearer token.
  • Clock skew and expiry. Expiry checks compare against PHP's clock. On containers with drifting time, a 60-second token can look already-expired. Check date on any box that mysteriously rejects fresh tokens.
  • Debugging failures is opaque by default. Exceptions come from the league layer, not Laravel, so they don't hit your usual handler cleanly. Temporarily log the caught exception class and message in the token route during setup — invalid_client, invalid_grant, and invalid_request each point at entirely different mistakes and the response body won't tell you which field was wrong.
  • Don't put the middleware on web routes. oauth2 expects a Authorization: Bearer header and will 401 browser navigation. Keep it scoped to API route groups only.
  • Migration timestamps may collide. Published migrations use the package's own dates; if they sort before your users table or a clients FK target, migrate breaks. Rename the published files to place them after your base tables.
  • Extension points to prefer, in order: custom repository bindings, the password-grant callback, then subclassing the service provider. Never edit files under vendor/ — a composer update will silently revert your fix.
  • Write an integration test against the token endpoint early. A curl-equivalent feature test that walks client credentials → token → protected route is the fastest way to catch config drift, and it's the test that will tell you whether a future migration off this package preserved behavior.
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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
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
cgv/localized-resource