raiolanetworks/simple-oauth2-client
Simple OAuth2 client for PHP and Laravel projects that streamlines authorization code flows, access token exchange, and refresh token handling. Lightweight and easy to configure, it helps integrate third-party OAuth providers and secure API authentication without boilerplate.
This package wires an OAuth authorization-code flow into a Laravel app: it registers two named routes, oauth.request and oauth.callback, and ships an install command that generates the config and tells you which redirect URI to register with your provider.
Minimal path from zero to a working login:
composer require <vendor>/<package>
php artisan oauth:install
oauth) and prints the redirect URI to paste into your OAuth provider's dashboard. Since 3.2.0 that URI is derived as APP_URL + the callback route path — e.g. https://example.com/oauth/callback, or https://example.com/auth/callback if you chose auth..env as prompted by the installer, and confirm APP_URL is the real public origin (scheme included).<a href="{{ route('oauth.request') }}">Sign in</a>. Route names never change, so you never have to touch these calls when you move the prefix.redirect_route_name_callback_ok.Where to look first when something is off:
config/oauth.php (or whatever the installer publishes) for route_prefix, preserve_intended_url, and the provider block.php artisan route:list --name=oauth to confirm the actual registered paths.oauth:install versus the one registered at the provider — these are the two values that must agree.The canonical first use case is ordinary "sign in with provider" for a web app, and the 3.2.0 defaults now give you the nicer behaviour for free: a user who follows a shared deep link, gets bounced to login, and authenticates ends up on the deep link rather than on a generic dashboard.
Protecting routes and letting the package handle the bounce. The intended-URL behaviour only pays off if unauthenticated users are actually redirected into the OAuth flow. Use the standard auth middleware on the protected group and let oauth.request be the login entry point; with preserve_intended_url => true the framework's stored URL is honoured on return. Keep your post-login fallback meaningful, because that is where users with no stored URL (direct visits to the login route, expired sessions) will land:
// config/oauth.php
'preserve_intended_url' => true,
'redirect_route_name_callback_ok' => 'dashboard',
Set preserve_intended_url => false for admin panels or apps where every login should funnel to one canonical screen — it removes a class of "why did I end up on a random page?" support tickets, at the cost of deep links not surviving login.
Moving the callback without breaking route() calls. route_prefix is the supported lever for path layout. A common pattern is to namespace it under an existing auth area so the flow reads consistently:
// .env
OAUTH_ROUTE_PREFIX=auth
{{-- still valid, path becomes /auth/callback --}}
{{ route('oauth.callback') }}
This is also the correct answer to "our reverse proxy/legacy app already owns /oauth". Change the prefix, re-run the installer (or read the printed URI), and register the new URI at the provider. Because route names are stable, no application code changes.
Environment-parity checklist per environment. Every environment has its own APP_URL and therefore its own advertised redirect URI. Keep a short table in your README of local/staging/production URIs and register all of them at the provider. Locally that means http://localhost:8000/oauth/callback (or your Sail/Valet host) — and note that the URI is now derived from APP_URL, not from the incoming request, so a mismatch between APP_URL and the host you actually browse to will send you to the wrong place.
Testing. Point your test suite at the named routes rather than hardcoded paths, so a prefix change doesn't churn tests:
$this->get(route('oauth.request'))->assertRedirect();
$this->get(route('oauth.callback'))->assertRedirect(route('dashboard'));
For the deep-link case, assert the intended URL is honoured after the callback, and add a companion test with preserve_intended_url disabled asserting the fallback route.
Deployment hygiene. Config and route caching capture the prefix and the derived callback path; after changing OAUTH_ROUTE_PREFIX, run php artisan config:cache && php artisan route:cache as part of the deploy, not ad hoc.
OAUTH_CALLBACK_URI is now inert. The oauth.callback config key is still read from existing published config files, and it can look authoritative while doing absolutely nothing. Any value you previously set there is ignored; the advertised URI follows the route. Treat editing it as a no-op and reach for OAUTH_ROUTE_PREFIX instead. If you have a stale non-default value sitting in a published config, delete it so the next developer isn't misled.
The one migration that needs action. If your app declared its own routes against OAuthController to serve the callback at a custom path and set OAUTH_CALLBACK_URI to match, the advertised URI has silently moved to the package route. Either set OAUTH_ROUTE_PREFIX to that same prefix so the package serves it where you expect, or register the new derived URI at the provider. Everything else upgrades without changes.
The redirect URI is built from APP_URL, deliberately. This is the fix that prevents the classic "authenticated successfully, then 404" failure. Consequences worth internalising:
TrustProxies unconfigured (the default in Laravel 11+), the app sees http:// internally. That no longer leaks into the advertised URI — but it does mean APP_URL must say https://, or you will register and advertise the wrong scheme.Host header cannot redirect the OAuth handshake to an attacker-controlled domain. Don't "fix" a perceived host mismatch by rebuilding the URI from $request; you'd reintroduce the vulnerability.127.0.0.1:8000, the URI follows APP_URL, not the address bar. If the provider rejects the redirect as unregistered, compare against APP_URL first, not against what you see in the browser.Debugging the mismatch in one step. When login fails at the provider with a redirect-URI error, print the value the app actually advertises (php artisan oauth:install reprints it, and route('oauth.callback') gives the path) and diff it character-for-character with the provider dashboard. Trailing slash, scheme, port, and www are all significant, and providers generally do exact string matching.
Route caching will hide prefix edits. If route:cache is in play, an .env change without a rebuild leaves the old path registered while config reports the new prefix — you get a 404 on a URL that "should" work. Clear and rebuild both caches when touching OAUTH_ROUTE_PREFIX.
preserve_intended_url is not a security boundary. It replays a URL stored in the session, so validate that your post-login targets are internal; don't let an intended URL become an open redirect if you ever populate it from a query parameter yourself.
Extension points to prefer. Route names (oauth.request, oauth.callback), redirect_route_name_callback_ok, and route_prefix are the stable, documented seams — build your login buttons, tests, and post-login redirects on those. Reaching into OAuthController with your own routes is precisely the pattern that 3.2.0 deprecated into a migration step; avoid new code that does it.
Upgrade checklist for 3.2.0. Confirm APP_URL is correct per environment and uses https in production; re-run oauth:install (or check route:list) and verify the printed redirect URI is registered at the provider in every environment; remove the dead OAUTH_CALLBACK_URI value; grep for OAuthController to catch hand-rolled callback routes; decide explicitly on preserve_intended_url; clear config and route caches.
How can I help you explore Laravel packages today?