alt-three/login
Alt Three Login is a Laravel package for adding social/OAuth authentication to your app. It provides a simple login flow that integrates with Laravel's auth system, giving developers a lightweight starting point for user sign-in and account handling.
alt-three/login is a Laravel 5 OAuth 2 authorization server built on league/oauth2-server. It ships the endpoints, Eloquent models, migrations, and middleware needed to issue and validate Bearer tokens. It does not ship a login UI, so it assumes you already have Laravel auth in place.
1. Install and pin. Because the package is archived (last release 2018-09-29), constrain it explicitly:
composer require alt-three/login:^1.0
Check composer.json for the required laravel/framework, php, and league/oauth2-server constraints before installing — this package predates Laravel 6+, so it will not run on modern Laravel without forking.
2. Register the provider. On Laravel 5.5+ look for extra.laravel.providers in the package's composer.json; if absent, add it manually in config/app.php:
AltThree\Login\LoginServiceProvider::class,
3. Publish and migrate.
php artisan vendor:publish --provider="AltThree\Login\LoginServiceProvider"
php artisan migrate
This should give you config/login.php and the oauth_clients, oauth_sessions, oauth_access_tokens, oauth_refresh_tokens, oauth_auth_codes, and oauth_scopes tables (verify exact names in the published migration).
4. Confirm the routes.
php artisan route:list | grep -i oauth
You should see the authorization and token endpoints (typically oauth/authorize and oauth/token). If they are missing, the provider was not registered or config is cached.
5. Create a client. There is no guaranteed artisan command, so seed it via the model (check the migration for the exact column names — usually id, secret, name, redirect):
AltThree\Login\Models\Client::create([
'id' => 'my-web-app',
'secret' => str_random(40),
'name' => 'My Web App',
'redirect' => 'https://app.example.com/oauth/callback',
]);
First use case: wire up an authorization-code flow for a first-party web app or a password-grant flow for a mobile/SPA client, then protect your API routes with a Bearer-token middleware. The tests directory in the repo is the most reliable source of real usage examples — read it before writing your own integration.
Authorization code flow (server-rendered / confidential clients).
/oauth/authorize?client_id=...&redirect_uri=...&response_type=code&scope=....login route handles sign-in, then the user is bounced back to the authorize screen.redirect_uri with ?code=....$response = $http->post(config('app.url').'/oauth/token', [
'form_params' => [
'grant_type' => 'authorization_code',
'client_id' => 'my-web-app',
'client_secret' => config('services.oauth.secret'),
'redirect_uri' => 'https://app.example.com/oauth/callback',
'code' => $request->code,
],
]);
Password grant (first-party SPA / mobile). Skip the redirect dance and post credentials directly to /oauth/token with grant_type=password, username, password, client_id, client_secret, and scope. The response returns access_token, refresh_token, and expires_in. Store both tokens client-side; refresh with grant_type=refresh_token.
Protecting API routes. Apply the package's OAuth middleware to your api group so a valid Bearer token resolves to an authenticated user. The middleware validates the token against the AccessToken model, checks expiry, and loads the associated Session. Verify the middleware alias in the service provider — you will likely need to register it yourself in app/Http/Kernel.php:
protected $routeMiddleware = [
'oauth' => \AltThree\Login\Http\Middleware\OAuthMiddleware::class,
];
Once the middleware runs, Auth::user() should return the session's user. If it doesn't, resolve the session explicitly from the request and call Auth::setUser($session->user) in a small follow-up middleware — this is a common integration point.
Scopes. Define scopes in the oauth_scopes table, and request them on the authorize/token call. Enforce them per-route by checking the session's granted scopes in middleware or a policy — the package validates that requested scopes exist, but it does not automatically gate your application logic on them.
Extending models and repositories. The package is designed to be swapped out: bind your own Client, Session, or AccessToken implementations in a service provider if you need extra columns (e.g., a tenant_id on clients) or custom token expiry logic. Register these bindings after the package provider so your implementation wins.
Session-to-user mapping. The Session model is the join between a Client, a user, and granted scopes. If your user model isn't the default App\User, check the config for a user model setting, or override the Session model to point at your own user() relation.
Housekeeping. Access tokens are short-lived, but refresh tokens, auth codes, and sessions accumulate. Schedule a daily prune of expired rows:
// app/Console/Kernel.php
$schedule->call(function () {
\AltThree\Login\Models\AccessToken::where('expires', '<', now())->delete();
\AltThree\Login\Models\AuthCode::where('expires', '<', now())->delete();
})->daily();
league/oauth2-server v5/v6. Do not run composer update blindly — a league major bump reintroduces PSR-7/PSR-15 BC breaks (repositories, AuthorizationServer constructor, exception interfaces). Fork and pin if this is going into production.extra.laravel exists.App\User table with an integer/bigint id. If your users table uses UUIDs, or you already have OAuth tables from a prior attempt, edit the migration before running it. Check foreign-key definitions and column types (user_id, client_id) against your schema.redirect_uri against the stored client value byte-for-byte. A trailing slash, http vs https, or a port difference will produce a invalid_client/invalid_grant error that is easy to miss because the response is often an opaque JSON error.invalid_grant is a catch-all. It covers expired/used codes, revoked refresh tokens, wrong client secrets, and expired access tokens. Enable Laravel's debug mode and inspect storage/logs/laravel.log; league's exceptions carry more detail than the HTTP response exposes.Carbon::now() in the app's timezone; league assumes UTC. If you run the app in a non-UTC timezone, tests will flake and tokens will expire early or late. Keep config/app.php timezone as UTC or convert explicitly.Auth::check() to be true. If you send users straight to /oauth/authorize unauthenticated, you'll get a redirect loop or a 401. Wrap the authorize route with your own auth middleware first.oauth_scopes fails validation. Seed your scope list in a migration or seeder, and keep the default scope in config/login.php consistent with it..env and reference them via config('services.oauth.*'); remember to run php artisan config:cache after changes in production and config:clear when debugging.AltThree\Login\Models\* calls through your controllers.ClientRepository/SessionRepository bindings, the config/login.php TTL and model settings, and the middleware alias. If you find yourself editing vendor code, you've missed a binding.How can I help you explore Laravel packages today?