danyseifeddine/keychain
最新稳定版本:1.1.2
Composer 安装命令:
composer require danyseifeddine/keychain
包简介
A Laravel package for simplified social authentication with multi-guard support
README 文档
README
The most powerful and flexible Laravel social authentication package with multi-guard support, automatic setup, and complete customization control.
KeyChain provides an elegant solution for implementing social login across multiple user types (Users, Restaurants, Admins, etc.) with automatic guard registration, dynamic user creation, polymorphic token storage, and infinitely customizable authentication flows.
🚀 Features
- 🔐 Multi-Guard Support - Support unlimited authentication guards (User, Restaurant, Admin, etc.)
- 🎯 Automatic Setup - Guards, providers, and routes registered automatically from config
- 🪄 Magic Methods - Dynamic method generation for guard-specific user creation/update
- 📦 Laravel Socialite Integration - Built on Laravel's official social authentication
- 🔄 Token Management - Polymorphic social token storage with advanced expiration control
- 🎨 Fully Customizable - Override any part of the authentication flow
- 🛡️ Security First - Secure token handling, validation, and cleanup
- 📊 Management Commands - Artisan commands for token management and statistics
- 🎭 Publishable Assets - Controllers, views, config, and migrations
- 🔧 Zero Configuration - Works out of the box with sensible defaults
📋 Table of Contents
- Installation
- Basic Configuration
- Simple Usage
- Multi-Guard Setup
- Publishing Assets
- Controller Customization
- Magic Methods
- Token Management
- Helper Functions
- Commands
- Advanced Configuration
- Examples
- Troubleshooting
🚀 Installation
1. Install via Composer
composer require danyseifeddine/keychain
2. Publish Configuration and Assets
# Publish everything (recommended for first time) php artisan vendor:publish --provider="Danyseifeddine\Keychain\KeyChainServiceProvider" # Or publish specific assets php artisan vendor:publish --tag=keychain-config php artisan vendor:publish --tag=keychain-migrations php artisan vendor:publish --tag=keychain-controller
3. Run Migrations
php artisan migrate
4. Add Social Provider Credentials
Add your OAuth credentials to .env:
# Google OAuth KEYCHAIN_GOOGLE_CLIENT_ID=your-google-client-id KEYCHAIN_GOOGLE_CLIENT_SECRET=your-google-client-secret # GitHub OAuth KEYCHAIN_GITHUB_CLIENT_ID=your-github-client-id KEYCHAIN_GITHUB_CLIENT_SECRET=your-github-client-secret # Facebook OAuth KEYCHAIN_FACEBOOK_CLIENT_ID=your-facebook-client-id KEYCHAIN_FACEBOOK_CLIENT_SECRET=your-facebook-client-secret # Custom redirect URLs (optional) KEYCHAIN_GOOGLE_REDIRECT_URL=http://127.0.0.1:8000/auth/google/callback KEYCHAIN_GITHUB_REDIRECT_URL=http://127.0.0.1:8000/auth/google/callback
⚙️ Basic Configuration
Update config/keychain.php to enable providers:
return [ // Enable social providers 'providers' => [ 'google' => true, // Enable Google OAuth 'github' => true, // Enable GitHub OAuth 'facebook' => false, // Disable Facebook OAuth ], // Basic routes configuration 'routes' => [ 'enabled' => true, 'prefix' => 'auth', 'middleware' => ['web'], ], // User management 'user' => [ 'auto_create' => true, 'auto_link' => true, 'update_on_login' => true, ], ];
🎯 Simple Usage
Basic Social Login (Single Guard)
KeyChain automatically generates these routes:
// Redirect to provider GET /auth/{provider}/redirect // Handle callback GET /auth/{provider}/callback // Unlink account DELETE /auth/{provider}/unlink
In your Blade template:
{{-- resources/views/auth/login.blade.php --}} <div class="social-login"> <h3>Login with Social Media</h3> @if(config('keychain.providers.google')) <a href="{{ route('keychain.redirect', 'google') }}" class="btn btn-primary"> Continue with Google </a> @endif @if(config('keychain.providers.github')) <a href="{{ route('keychain.redirect', 'github') }}" class="btn btn-dark"> Continue with GitHub </a> @endif @if(config('keychain.providers.facebook')) <a href="{{ route('keychain.redirect', 'facebook') }}" class="btn btn-primary"> Continue with Facebook </a> @endif </div>
User Model Setup
Add the HasSocialAccounts trait to your User model:
<?php namespace App\Models; use Illuminate\Foundation\Auth\User as Authenticatable; use Danyseifeddine\Keychain\Traits\HasSocialAccounts; class User extends Authenticatable { use HasSocialAccounts; protected $fillable = [ 'name', 'email', 'password', 'avatar_url' ]; }
That's it! KeyChain will automatically:
- ✅ Handle OAuth redirects and callbacks
- ✅ Create new users from social data
- ✅ Link social accounts to existing users
- ✅ Store and manage social tokens
- ✅ Redirect users to dashboard after login
🛡️ Multi-Guard Setup
1. Enable Multi-Guard in Config
// config/keychain.php return [ 'guards' => [ 'enabled' => true, 'default_guard' => 'web', 'available_guards' => [ // Regular users 'web' => [ 'enabled' => true, 'model' => App\Models\User::class, 'table' => 'users', 'name' => 'User', 'redirect_after_auth' => '/dashboard', 'middleware' => ['web'], ], // Restaurant owners 'restaurant' => [ 'enabled' => true, 'model' => App\Models\Restaurant::class, 'table' => 'restaurants', 'name' => 'Restaurant', 'redirect_after_auth' => '/restaurant-dashboard', 'middleware' => ['web'], ], // Admin users 'admin' => [ 'enabled' => true, 'model' => App\Models\Admin::class, 'table' => 'admins', 'name' => 'Administrator', 'redirect_after_auth' => '/admin/dashboard', 'middleware' => ['web', 'admin'], ], ], ], ];
2. Create Guard Models
<?php // app/Models/Restaurant.php namespace App\Models; use Illuminate\Foundation\Auth\User as Authenticatable; use Danyseifeddine\Keychain\Traits\HasSocialAccounts; class Restaurant extends Authenticatable { use HasSocialAccounts; protected $fillable = [ 'name', 'email', 'password', 'website', 'description' ]; }
3. Multi-Guard Routes
KeyChain automatically generates guard-specific routes:
// Generic routes (uses default guard or guard parameter) GET /auth/{provider}/redirect?guard=restaurant GET /auth/{provider}/callback?guard=restaurant // Guard-specific routes (recommended) GET /auth/restaurant/{provider}/redirect GET /auth/restaurant/{provider}/callback GET /auth/admin/{provider}/redirect GET /auth/admin/{provider}/callback
4. Multi-Guard Login Template
{{-- resources/views/auth/multi-login.blade.php --}} <div class="multi-guard-login"> <!-- User Login --> <div class="guard-section"> <h4>Login as User</h4> <a href="{{ route('keychain.web.redirect', 'google') }}" class="btn btn-primary"> Google (User) </a> <a href="{{ route('keychain.web.redirect', 'github') }}" class="btn btn-dark"> GitHub (User) </a> </div> <!-- Restaurant Login --> <div class="guard-section"> <h4>Login as Restaurant</h4> <a href="{{ route('keychain.restaurant.redirect', 'google') }}" class="btn btn-success"> Google (Restaurant) </a> <a href="{{ route('keychain.restaurant.redirect', 'github') }}" class="btn btn-info"> GitHub (Restaurant) </a> </div> <!-- Admin Login --> <div class="guard-section"> <h4>Login as Admin</h4> <a href="{{ route('keychain.admin.redirect', 'google') }}" class="btn btn-warning"> Google (Admin) </a> <a href="{{ route('keychain.admin.redirect', 'github') }}" class="btn btn-secondary"> GitHub (Admin) </a> </div> </div>
📦 Publishing Assets
KeyChain provides several publishable assets for customization:
Available Publishing Tags
# Publish configuration file php artisan vendor:publish --tag=keychain-config # Publish database migrations php artisan vendor:publish --tag=keychain-migrations # Publish customizable controller php artisan vendor:publish --tag=keychain-controller # Publish environment configuration stub php artisan vendor:publish --tag=keychain-env # Publish everything php artisan vendor:publish --provider="Danyseifeddine\Keychain\KeyChainServiceProvider"
What Gets Published
| Tag | Files Published | Purpose |
|---|---|---|
keychain-config |
config/keychain.php |
Main configuration file |
keychain-migrations |
database/migrations/xxx_create_key_chain_tokens_table.php |
Token storage migration |
keychain-views |
resources/views/keychain/ |
Login and dashboard templates |
keychain-controller |
app/Http/Controllers/Auth/SocialAuthController.php |
Customizable controller |
keychain-env |
keychain.env |
Environment variables template |
🎛️ Controller Customization
Publishing the Controller
php artisan vendor:publish --tag=keychain-controller
This creates app/Http/Controllers/Auth/SocialAuthController.php which extends the base controller.
Magic Methods (Dynamic Guard Support)
KeyChain automatically looks for guard-specific methods:
<?php namespace App\Http\Controllers\Auth; use Danyseifeddine\Keychain\Http\Controllers\SocialAuthController as BaseController; class SocialAuthController extends BaseController { /** * 🪄 MAGIC METHODS - KeyChain automatically calls these based on guard name * * Pattern: create{Guard}User() and update{Guard}User() * Examples: createWebUser(), createRestaurantUser(), createAdminUser() */ /** * Custom user creation for 'web' guard */ protected function createWebUser($socialiteUser) { return \App\Models\User::create([ 'name' => $socialiteUser->getName(), 'email' => $socialiteUser->getEmail(), 'email_verified_at' => now(), 'password' => bcrypt(str()->random(12)), 'avatar_url' => $socialiteUser->getAvatar(), ]); } /** * Custom restaurant creation for 'restaurant' guard */ protected function createRestaurantUser($socialiteUser) { return \App\Models\Restaurant::create([ 'name' => 'Custom Restaurant Name', // Your custom logic 'email' => $socialiteUser->getEmail(), 'website' => 'https://example.com', 'description' => 'Restaurant created via social authentication', 'password' => bcrypt(str()->random(12)), 'status' => 'active', ]); } /** * Custom admin creation for 'admin' guard */ protected function createAdminUser($socialiteUser) { // Custom validation - only allow specific emails as admins $allowedEmails = ['admin@example.com', 'boss@example.com']; if (!in_array($socialiteUser->getEmail(), $allowedEmails)) { throw new \Exception('Unauthorized admin access attempt'); } return \App\Models\Admin::create([ 'name' => $socialiteUser->getName(), 'email' => $socialiteUser->getEmail(), 'role' => 'admin', 'permissions' => ['manage_users', 'view_reports'], 'password' => bcrypt(str()->random(12)), ]); } /** * Update user data for 'web' guard */ protected function updateWebUser($user, $socialiteUser) { $user->update([ 'name' => $socialiteUser->getName(), 'avatar_url' => $socialiteUser->getAvatar(), 'last_login_at' => now(), ]); return $user; } /** * Update restaurant data for 'restaurant' guard */ protected function updateRestaurantUser($restaurant, $socialiteUser) { // Don't update name for restaurants - keep original $restaurant->update([ 'last_login_at' => now(), 'social_avatar' => $socialiteUser->getAvatar(), ]); return $restaurant; } }
Lifecycle Hooks
Override these methods for custom logic at different stages:
class SocialAuthController extends BaseController { /** * Called before redirecting to social provider */ protected function beforeRedirect(string $provider): void { // Log authentication attempts Log::info("Social auth redirect to {$provider}", [ 'ip' => request()->ip(), 'user_agent' => request()->userAgent(), ]); // Custom logic before redirect if ($provider === 'github' && !config('app.allow_github')) { throw new Exception('GitHub authentication is temporarily disabled'); } } /** * Called after successful authentication */ protected function afterAuthentication(string $provider, $socialiteUser, $token, $user): void { // Send welcome email if ($user->wasRecentlyCreated) { Mail::to($user)->send(new WelcomeEmail($user, $provider)); } // Log successful authentication Log::info("User authenticated via {$provider}", [ 'user_id' => $user->id, 'provider' => $provider, ]); // Update user statistics $user->increment('login_count'); $user->update(['last_social_login' => now()]); } /** * Called when authentication fails */ protected function onAuthenticationFailure(string $provider, \Exception $exception): ?\Illuminate\Http\RedirectResponse { // Log the error Log::error("Social auth failed for {$provider}", [ 'error' => $exception->getMessage(), 'trace' => $exception->getTraceAsString(), ]); // Custom error handling if ($exception instanceof RateLimitExceededException) { return redirect('/login') ->with('error', 'Too many authentication attempts. Please try again later.'); } // Return null to use default error handling return null; } }
Complete Callback Override
For ultimate control, override the entire authentication flow:
class SocialAuthController extends BaseController { /** * Completely override the authentication callback * Return null to use KeyChain's default flow */ protected function handleCustomCallback(string $provider): ?\Illuminate\Http\RedirectResponse { // Your completely custom authentication logic $socialiteUser = Socialite::driver($provider)->user(); // Custom user creation/retrieval logic $user = $this->myCustomUserLogic($socialiteUser, $provider); // Custom token storage $this->myCustomTokenStorage($user, $socialiteUser, $provider); // Custom login Auth::login($user); // Custom redirect return redirect('/my-custom-dashboard') ->with('success', "Welcome! You've been authenticated via {$provider}"); } private function myCustomUserLogic($socialiteUser, $provider) { // Your custom logic here... return User::firstOrCreate( ['email' => $socialiteUser->getEmail()], [ 'name' => $socialiteUser->getName(), 'password' => bcrypt(str()->random(16)), 'email_verified_at' => now(), 'provider' => $provider, 'custom_field' => 'custom_value', ] ); } }
🪄 Magic Methods
KeyChain's magic method system automatically handles guard-specific user creation and updates without requiring you to define every method.
How Magic Methods Work
- Method Pattern:
create{Guard}User()andupdate{Guard}User() - Automatic Detection: KeyChain detects the guard from the method name
- Dynamic Fallback: If method doesn't exist, uses dynamic creation from config
- Case Handling: Converts method names to proper guard names automatically
Examples
| Guard Name | Create Method | Update Method |
|---|---|---|
web |
createWebUser() |
updateWebUser() |
restaurant |
createRestaurantUser() |
updateRestaurantUser() |
admin |
createAdminUser() |
updateAdminUser() |
merchant |
createMerchantUser() |
updateMerchantUser() |
customer_support |
createCustomerSupportUser() |
updateCustomerSupportUser() |
Dynamic Creation (When Methods Don't Exist)
If you don't define custom methods, KeyChain automatically creates users using the guard's model and configuration:
// If createRestaurantUser() doesn't exist, KeyChain does this automatically: protected function dynamicCreateUser(string $guardName, $socialiteUser) { $guardConfig = $this->getGuardConfig($guardName); // Gets restaurant config $modelClass = $guardConfig['model']; // App\Models\Restaurant::class return $modelClass::create([ 'name' => $socialiteUser->getName(), 'email' => $socialiteUser->getEmail(), 'email_verified_at' => now(), 'password' => bcrypt(str()->random(12)), // Maps other fields from config ]); }
Enabling/Disabling Magic Methods
// config/keychain.php 'advanced' => [ 'dynamic_methods' => true, // Enable magic methods 'method_prefixes' => ['create', 'update'], // Supported prefixes ],
Magic Method Debugging
Enable debug mode to see magic method calls:
// config/keychain.php 'advanced' => [ 'debug_mode' => true, // Enable debug logging ],
Check storage/logs/laravel.log for magic method calls:
[2024-07-19 12:00:00] local.DEBUG: KeyChain Magic Method Called: createRestaurantUser
[2024-07-19 12:00:00] local.DEBUG: KeyChain Dynamic Creation for guard: restaurant
🔐 Token Management
KeyChain provides comprehensive token management with polymorphic relationships, expiration control, and automatic cleanup.
Token Storage Schema
Tokens are stored in the key_chain_tokens table with polymorphic relationships:
CREATE TABLE key_chain_tokens ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, provider VARCHAR(255) NOT NULL, -- 'google', 'github', etc. provider_id VARCHAR(255) NOT NULL, -- Provider's user ID access_token TEXT, -- OAuth access token expires_at TIMESTAMP NULL, -- Token expiration (nullable for infinite tokens) user_data JSON NULL, -- Cached provider user data tokenable_type VARCHAR(255) NOT NULL, -- Model class (User, Restaurant, etc.) tokenable_id BIGINT UNSIGNED NOT NULL, -- Model ID created_at TIMESTAMP NULL, updated_at TIMESTAMP NULL, INDEX (provider, provider_id), UNIQUE KEY unique_social_account (provider, provider_id, tokenable_type, tokenable_id) );
Token Expiration Configuration
// config/keychain.php 'tokens' => [ 'store_tokens' => true, // Enable token storage 'cleanup_expired' => true, // Auto-cleanup expired tokens 'cleanup_days' => 30, // Delete expired tokens after X days 'expiration' => [ 'infinite' => false, // Set to true for tokens that never expire 'default_expiry' => 3600, // Default expiry in seconds (1 hour) // Custom expiry per provider 'custom_expiry' => [ 'google' => 7200, // 2 hours for Google tokens 'github' => 86400, // 24 hours for GitHub tokens 'facebook' => 3600, // 1 hour for Facebook tokens ], ], ],
Setting Infinite Tokens (Never Expire)
// config/keychain.php 'tokens' => [ 'expiration' => [ 'infinite' => true, // 🔄 Tokens never expire ], ],
When infinite is true, all tokens get expires_at = null and never expire.
Custom Token Expiration Logic
Override the token expiration logic in your controller:
class SocialAuthController extends BaseController { /** * Custom token expiration logic */ protected function determineTokenExpiration(string $provider, ?int $providerExpiresIn): ?\DateTime { // Different expiration rules per provider return match ($provider) { 'google' => now()->addHours(2), // 2 hours for Google 'github' => now()->addDays(7), // 7 days for GitHub 'facebook' => now()->addHour(), // 1 hour for Facebook 'admin_provider' => null, // Infinite for admin provider default => now()->addHours(1), // 1 hour for others }; } }
Working with Tokens in Your Models
Using the HasSocialAccounts trait:
class User extends Authenticatable { use HasSocialAccounts; // ... model code } // Usage examples: $user = User::find(1); // Get all social accounts $accounts = $user->socialAccounts; // Get specific provider account $googleAccount = $user->getSocialAccount('google'); // Check if user has provider if ($user->hasSocialAccount('github')) { echo "User has GitHub account"; } // Check if token is still valid if ($user->hasValidSocialToken('google')) { echo "Google token is still valid"; } // Get connected providers $providers = $user->getConnectedProviders(); // ['google', 'github'] // Link new social account manually $user->linkSocialAccount( 'twitter', 'twitter_user_id_123', 'access_token_here', now()->addDays(30), // expires in 30 days ['name' => 'John Doe', 'avatar' => 'avatar_url'] ); // Unlink social account $user->unlinkSocialAccount('facebook');
Token Model Methods
The KeyChainToken model provides useful methods:
$token = KeyChainToken::find(1); // Check token validity $token->isValid(); // true if not expired $token->isExpired(); // true if expired // Get cached user data $token->getName(); // User's name from provider $token->getEmail(); // User's email from provider $token->getAvatar(); // User's avatar URL from provider $token->getNickname(); // User's nickname from provider // Get the associated user/model $user = $token->tokenable; // Returns User, Restaurant, Admin, etc.
🛠️ Helper Functions
KeyChain provides a comprehensive helper class with utility methods for social authentication management.
KeyChainHelper Class
All helper methods are available via the KeyChainHelper class:
use Danyseifeddine\Keychain\Http\Helpers\KeyChainHelper;
Provider Management
// Get all enabled providers $providers = KeyChainHelper::getEnabledProviders(); // Returns: ['google', 'github', 'facebook'] // Check if a provider is enabled and configured if (KeyChainHelper::isProviderReady('google')) { echo "Google OAuth is ready to use"; } // Get status of all providers $statuses = KeyChainHelper::getProviderStatuses(); /* Returns: [ 'google' => [ 'enabled' => true, 'configured' => true, 'name' => 'Google' ], 'github' => [ 'enabled' => true, 'configured' => false, // Missing credentials 'name' => 'Github' ] ] */
Guard Management
// Get all enabled guards $guards = KeyChainHelper::getEnabledGuards(); /* Returns: [ 'web' => [ 'enabled' => true, 'model' => 'App\Models\User', 'table' => 'users', 'name' => 'User', // ... other config ], 'restaurant' => [ 'enabled' => true, 'model' => 'App\Models\Restaurant', // ... other config ] ] */ // Get configuration for specific guard $guardConfig = KeyChainHelper::getGuardConfig('restaurant'); // Check if guard is enabled if (KeyChainHelper::isGuardEnabled('admin')) { echo "Admin guard is enabled"; } // Get currently authenticated guard $currentGuard = KeyChainHelper::getCurrentAuthenticatedGuard(); // Returns: 'web', 'restaurant', 'admin', or null // Get currently authenticated user from any guard $currentUser = KeyChainHelper::getCurrentAuthenticatedUser(); // Returns: User, Restaurant, Admin model, or null
URL Generation
// Get social authentication URLs for specific guard $urls = KeyChainHelper::getSocialAuthUrls('restaurant'); /* Returns: [ 'google' => 'https://yourapp.com/auth/restaurant/google/redirect', 'github' => 'https://yourapp.com/auth/restaurant/github/redirect' ] */ // Get URLs for default guard $defaultUrls = KeyChainHelper::getSocialAuthUrls(); // Get URL for specific provider and guard $googleRestaurantUrl = route('keychain.restaurant.redirect', 'google');
User Social Account Management
// Get user's social accounts (current authenticated user) $accounts = KeyChainHelper::getUserSocialAccounts(); // Get specific user's accounts for specific guard $accounts = KeyChainHelper::getUserSocialAccounts(123, 'restaurant'); /* Returns: [ [ 'provider' => 'google', 'name' => 'John Doe', 'email' => 'john@example.com', 'avatar' => 'https://avatar-url.com', 'connected_at' => Carbon instance, 'is_valid' => true, 'expires_at' => Carbon instance or null ], // ... more accounts ] */ // Check if user has any social accounts if (KeyChainHelper::hasSocialAccounts(123, 'web')) { echo "User has connected social accounts"; }
Token Cleanup
// Clean up expired tokens (returns number of deleted tokens) $cleanedUp = KeyChainHelper::cleanupExpiredTokens(); echo "Cleaned up {$cleanedUp} expired tokens";
Authentication Statistics
// Get comprehensive authentication statistics $stats = KeyChainHelper::getAuthStatistics(); /* Returns: [ 'total_tokens' => 150, 'valid_tokens' => 120, 'expired_tokens' => 30, 'providers' => [ 'google' => 80, 'github' => 45, 'facebook' => 25 ], 'guards' => [ 'web' => 100, 'restaurant' => 35, 'admin' => 15 ], 'recent_activity' => [ 'last_7_days' => 25, 'last_30_days' => 60 ] ] */
Using Helpers in Blade Templates
{{-- Get current authenticated user across all guards --}} @php $currentUser = KeyChainHelper::getCurrentAuthenticatedUser(); $currentGuard = KeyChainHelper::getCurrentAuthenticatedGuard(); @endphp @if($currentUser) <p>Welcome {{ $currentUser->name }} ({{ $currentGuard }} guard)</p> {{-- Show social accounts --}} @php $socialAccounts = KeyChainHelper::getUserSocialAccounts(); @endphp @if($socialAccounts) <h5>Connected Accounts:</h5> @foreach($socialAccounts as $account) <div class="social-account"> <img src="{{ $account['avatar'] }}" width="32" height="32"> {{ ucfirst($account['provider']) }} - {{ $account['name'] }} @if(!$account['is_valid']) <span class="badge badge-warning">Expired</span> @endif </div> @endforeach @endif @endif {{-- Show available providers --}} @php $providerStatuses = KeyChainHelper::getProviderStatuses(); @endphp <div class="available-providers"> @foreach($providerStatuses as $provider => $status) @if($status['enabled'] && $status['configured']) <a href="{{ route('keychain.redirect', $provider) }}" class="btn btn-{{ $provider }}"> Login with {{ $status['name'] }} </a> @endif @endforeach </div>
Custom Helper Usage in Controllers
class DashboardController extends Controller { public function index() { $user = KeyChainHelper::getCurrentAuthenticatedUser(); $guard = KeyChainHelper::getCurrentAuthenticatedGuard(); $socialAccounts = KeyChainHelper::getUserSocialAccounts(); $stats = KeyChainHelper::getAuthStatistics(); return view('dashboard', compact('user', 'guard', 'socialAccounts', 'stats')); } public function socialStats() { // Admin-only social authentication statistics return response()->json([ 'statistics' => KeyChainHelper::getAuthStatistics(), 'provider_statuses' => KeyChainHelper::getProviderStatuses(), 'enabled_guards' => KeyChainHelper::getEnabledGuards(), ]); } }
🎯 Commands
KeyChain provides powerful Artisan commands for managing social authentication tokens and providers.
Available Commands
# Show all available KeyChain commands
php artisan keychain --help
Provider Management
# List all configured providers and their status php artisan keychain providers # Example output: # ┏━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓ # ┃ Provider ┃ Status ┃ Config Status ┃ # ┣━━━━━━━━━━━╋━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━┫ # ┃ google ┃ ✅ Enabled ┃ ✅ Configured ┃ # ┃ github ┃ ✅ Enabled ┃ ❌ Missing Config ┃ # ┃ facebook ┃ ❌ Disabled ┃ ❌ Missing Config ┃ # ┗━━━━━━━━━━━┻━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━┛
Token Management
# List all stored social tokens php artisan keychain tokens # Filter tokens by provider php artisan keychain tokens --provider=google # Filter tokens by user ID php artisan keychain tokens --user=123 # Show only expired tokens php artisan keychain tokens --expired # Combine filters php artisan keychain tokens --provider=github --expired # Example output: # ┏━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━┓ # ┃ ID ┃ Provider ┃ User ┃ Status ┃ Created ┃ Expires ┃ # ┣━━━━╋━━━━━━━━━━╋━━━━━━━━━━━━━╋━━━━━━━━━━╋━━━━━━━━━━━━╋━━━━━━━━━━━━━┫ # ┃ 1 ┃ Google ┃ John Doe ┃ ✅ Valid ┃ Jul 1, 2024┃ Jul 2, 2024 ┃ # ┃ 2 ┃ Github ┃ Jane Smith ┃ ❌ Expired┃ Jun 15, 2024┃ Jun 16, 2024┃ # ┗━━━━┻━━━━━━━━━━┻━━━━━━━━━━━━━┻━━━━━━━━━━┻━━━━━━━━━━━━┻━━━━━━━━━━━━━┛
Token Cleanup
# Clean up expired tokens (with confirmation) php artisan keychain cleanup # Clean up without confirmation php artisan keychain cleanup --force # Clean up expired tokens for specific provider php artisan keychain cleanup --provider=google # Clean up expired tokens for specific user php artisan keychain cleanup --user=123 # Example output: # ⚠️ This will permanently delete expired social authentication tokens. # # Found 15 expired tokens to clean up: # - Google: 8 tokens # - GitHub: 5 tokens # - Facebook: 2 tokens # # Do you want to continue? (yes/no) [no]: yes # # ✅ Successfully cleaned up 15 expired tokens.
Token Revocation
# Revoke all tokens for a specific provider php artisan keychain revoke --provider=google # Revoke all tokens for a specific user php artisan keychain revoke --user=123 # Revoke all tokens for user and provider combination php artisan keychain revoke --user=123 --provider=github # Revoke without confirmation php artisan keychain revoke --provider=facebook --force # Example output: # ⚠️ This will revoke social authentication tokens. # # Found 5 tokens to revoke for provider 'google': # - User #123: John Doe (john@example.com) # - User #456: Jane Smith (jane@example.com) # - Restaurant #789: Pizza Palace (pizza@example.com) # # Do you want to continue? (yes/no) [no]: yes # # ✅ Successfully revoked 5 tokens for provider 'google'.
Statistics
# Show comprehensive KeyChain statistics php artisan keychain stats # Example output: # 📊 KeyChain Statistics # # Total Tokens: 156 # Valid Tokens: 134 # Expired Tokens: 22 # # Tokens by Provider: # google: 85 # github: 45 # facebook: 26 # # New tokens (last 7 days): 12 # Users with social accounts: 98
Automated Token Cleanup
Set up automated token cleanup using Laravel's scheduler:
// app/Console/Kernel.php protected function schedule(Schedule $schedule) { // Clean up expired tokens daily at 2 AM $schedule->command('keychain cleanup --force') ->daily() ->at('02:00') ->withoutOverlapping(); // Generate weekly statistics $schedule->command('keychain stats') ->weeklyOn(1, '09:00') // Mondays at 9 AM ->appendOutputTo(storage_path('logs/keychain-stats.log')); }
Command Options Summary
| Command | Options | Description |
|---|---|---|
keychain providers |
None | List provider status |
keychain tokens |
--provider, --user, --expired |
List and filter tokens |
keychain cleanup |
--provider, --user, --force |
Clean expired tokens |
keychain revoke |
--provider, --user, --force |
Revoke active tokens |
keychain stats |
None | Show statistics |
⚙️ Advanced Configuration
Complete Configuration Reference
<?php // config/keychain.php return [ /* |-------------------------------------------------------------------------- | Authentication Guards Configuration |-------------------------------------------------------------------------- */ 'guards' => [ 'enabled' => true, // Enable multi-guard functionality 'default_guard' => 'web', // Default guard when none specified 'available_guards' => [ 'web' => [ 'enabled' => true, 'model' => App\Models\User::class, 'table' => 'users', 'name' => 'User', 'redirect_after_auth' => '/dashboard', 'middleware' => ['web'], ], // Add more guards as needed... ], ], /* |-------------------------------------------------------------------------- | Social Provider Services Configuration |-------------------------------------------------------------------------- */ 'services' => [ 'google' => [ 'client_id' => env('KEYCHAIN_GOOGLE_CLIENT_ID'), 'client_secret' => env('KEYCHAIN_GOOGLE_CLIENT_SECRET'), 'redirect' => env('KEYCHAIN_GOOGLE_REDIRECT_URL', env('APP_URL') . '/auth/google/callback'), ], // Add more providers... ], /* |-------------------------------------------------------------------------- | Enabled Social Providers |-------------------------------------------------------------------------- */ 'providers' => [ 'google' => true, // Enable Google OAuth 'github' => false, // Disable GitHub OAuth 'facebook' => false, // Disable Facebook OAuth 'twitter' => false, 'linkedin' => false, 'bitbucket' => false, 'gitlab' => false, ], /* |-------------------------------------------------------------------------- | Route Configuration |-------------------------------------------------------------------------- */ 'routes' => [ 'enabled' => true, // Enable automatic route registration 'prefix' => 'auth', // URL prefix (/auth/google/redirect) 'middleware' => ['web'], // Default middleware stack 'guard_parameter' => 'guard', // Route parameter for guard detection ], /* |-------------------------------------------------------------------------- | User Management Configuration |-------------------------------------------------------------------------- */ 'user' => [ 'auto_create' => true, // Auto-create users on first login 'auto_link' => true, // Link accounts to existing users by email 'update_on_login' => true, // Update user data on each login 'fillable_fields' => [ 'name' => 'name', // Map provider 'name' to model 'name' 'email' => 'email', // Map provider 'email' to model 'email' 'avatar' => 'avatar_url', // Map provider 'avatar' to model 'avatar_url' ], ], /* |-------------------------------------------------------------------------- | Token Management Configuration |-------------------------------------------------------------------------- */ 'tokens' => [ 'store_tokens' => true, // Store OAuth tokens in database 'cleanup_expired' => true, // Auto-cleanup expired tokens 'cleanup_days' => 30, // Delete expired tokens after X days 'expiration' => [ 'infinite' => false, // Set true for tokens that never expire 'default_expiry' => 3600, // Default expiry in seconds (1 hour) // Custom expiry per provider (in seconds) 'custom_expiry' => [ 'google' => 7200, // 2 hours for Google 'github' => 86400, // 24 hours for GitHub 'facebook' => 3600, // 1 hour for Facebook ], ], ], /* |-------------------------------------------------------------------------- | Redirect Configuration |-------------------------------------------------------------------------- */ 'redirects' => [ 'success' => '/dashboard', // Default success redirect 'failure' => '/login', // Default failure redirect ], /* |-------------------------------------------------------------------------- | Advanced Configuration |-------------------------------------------------------------------------- */ 'advanced' => [ 'debug_mode' => false, // Enable debug logging (NOT for production) 'enable_custom_callbacks' => false, // Enable complete callback override 'dynamic_methods' => true, // Enable magic method generation 'method_prefixes' => ['create', 'update'], // Supported magic method prefixes ], ];
Environment Variables Reference
# Core Configuration KEYCHAIN_ENABLED=true # Google OAuth KEYCHAIN_GOOGLE_CLIENT_ID=your-google-client-id KEYCHAIN_GOOGLE_CLIENT_SECRET=your-google-client-secret KEYCHAIN_GOOGLE_REDIRECT_URL=https://yourapp.com/auth/google/callback # GitHub OAuth KEYCHAIN_GITHUB_CLIENT_ID=your-github-client-id KEYCHAIN_GITHUB_CLIENT_SECRET=your-github-client-secret KEYCHAIN_GITHUB_REDIRECT_URL=https://yourapp.com/auth/github/callback # Facebook OAuth KEYCHAIN_FACEBOOK_CLIENT_ID=your-facebook-client-id KEYCHAIN_FACEBOOK_CLIENT_SECRET=your-facebook-client-secret KEYCHAIN_FACEBOOK_REDIRECT_URL=https://yourapp.com/auth/facebook/callback # Twitter OAuth KEYCHAIN_TWITTER_CLIENT_ID=your-twitter-client-id KEYCHAIN_TWITTER_CLIENT_SECRET=your-twitter-client-secret KEYCHAIN_TWITTER_REDIRECT_URL=https://yourapp.com/auth/twitter/callback # LinkedIn OAuth KEYCHAIN_LINKEDIN_CLIENT_ID=your-linkedin-client-id KEYCHAIN_LINKEDIN_CLIENT_SECRET=your-linkedin-client-secret KEYCHAIN_LINKEDIN_REDIRECT_URL=https://yourapp.com/auth/linkedin/callback # Advanced Settings KEYCHAIN_DEBUG_MODE=false KEYCHAIN_TOKEN_INFINITE=false KEYCHAIN_DEFAULT_EXPIRY=3600 KEYCHAIN_CLEANUP_DAYS=30
Adding Custom Providers
KeyChain supports any Laravel Socialite provider. To add a custom provider:
- Install the provider package:
composer require socialiteproviders/discord
- Add to services config:
// config/keychain.php 'services' => [ 'discord' => [ 'client_id' => env('KEYCHAIN_DISCORD_CLIENT_ID'), 'client_secret' => env('KEYCHAIN_DISCORD_CLIENT_SECRET'), 'redirect' => env('KEYCHAIN_DISCORD_REDIRECT_URL', env('APP_URL') . '/auth/discord/callback'), ], ], 'providers' => [ 'discord' => true, // Enable Discord ],
- Add environment variables:
KEYCHAIN_DISCORD_CLIENT_ID=your-discord-client-id KEYCHAIN_DISCORD_CLIENT_SECRET=your-discord-client-secret
- Register the provider (if needed):
// config/services.php or in a service provider Event::listen(function (\SocialiteProviders\Manager\SocialiteWasCalled $event) { $event->extendSocialite('discord', \SocialiteProviders\Discord\Provider::class); });
Custom Middleware
Add custom middleware to routes:
// config/keychain.php 'routes' => [ 'middleware' => ['web', 'throttle:social-auth'], ], // app/Http/Kernel.php protected $middlewareGroups = [ 'web' => [ // ... existing middleware ], ]; protected $namedMiddleware = [ 'throttle:social-auth' => \Illuminate\Routing\Middleware\ThrottleRequests::class . ':10,1', // 10 requests per minute ];
Performance Optimization
// config/keychain.php 'tokens' => [ 'cleanup_expired' => true, // Auto-cleanup improves performance 'cleanup_days' => 7, // Shorter retention for better performance ], 'advanced' => [ 'debug_mode' => false, // Always false in production ],
Security Best Practices
// config/keychain.php 'user' => [ 'auto_link' => false, // Disable for higher security 'update_on_login' => false, // Prevent data overwriting ], 'tokens' => [ 'expiration' => [ 'infinite' => false, // Always use token expiration 'default_expiry' => 1800, // 30 minutes for sensitive apps ], },
💡 Examples
Complete Multi-Restaurant Application
This example shows a complete setup for a multi-restaurant platform where restaurant owners can log in to manage their restaurants.
1. Configuration
// config/keychain.php return [ 'guards' => [ 'enabled' => true, 'available_guards' => [ 'web' => [ 'enabled' => true, 'model' => App\Models\User::class, 'table' => 'users', 'name' => 'Customer', 'redirect_after_auth' => '/customer/dashboard', ], 'restaurant' => [ 'enabled' => true, 'model' => App\Models\Restaurant::class, 'table' => 'restaurants', 'name' => 'Restaurant Owner', 'redirect_after_auth' => '/restaurant/dashboard', ], ], ], 'providers' => [ 'google' => true, 'github' => true, ], ];
2. Models
// app/Models/Restaurant.php <?php namespace App\Models; use Illuminate\Foundation\Auth\User as Authenticatable; use Danyseifeddine\Keychain\Traits\HasSocialAccounts; class Restaurant extends Authenticatable { use HasSocialAccounts; protected $fillable = [ 'name', 'email', 'password', 'website', 'description', 'cuisine_type', 'phone', 'address', 'status' ]; protected $hidden = ['password']; protected $casts = [ 'email_verified_at' => 'datetime', 'last_login_at' => 'datetime', ]; } // app/Models/User.php (Customer) <?php namespace App\Models; use Illuminate\Foundation\Auth\User as Authenticatable; use Danyseifeddine\Keychain\Traits\HasSocialAccounts; class User extends Authenticatable { use HasSocialAccounts; protected $fillable = [ 'name', 'email', 'password', 'phone', 'avatar_url' ]; }
3. Custom Controller
// app/Http/Controllers/Auth/SocialAuthController.php <?php namespace App\Http\Controllers\Auth; use Danyseifeddine\Keychain\Http\Controllers\SocialAuthController as BaseController; use App\Models\User; use App\Models\Restaurant; use Illuminate\Support\Facades\Mail; use App\Mail\RestaurantWelcomeEmail; class SocialAuthController extends BaseController { /** * Custom customer (web guard) creation */ protected function createWebUser($socialiteUser) { $user = User::create([ 'name' => $socialiteUser->getName(), 'email' => $socialiteUser->getEmail(), 'email_verified_at' => now(), 'password' => bcrypt(str()->random(12)), 'avatar_url' => $socialiteUser->getAvatar(), ]); // Send welcome email Mail::to($user)->send(new CustomerWelcomeEmail($user)); return $user; } /** * Custom restaurant creation with business logic */ protected function createRestaurantUser($socialiteUser) { $restaurant = Restaurant::create([ 'name' => $this->generateRestaurantName($socialiteUser), 'email' => $socialiteUser->getEmail(), 'email_verified_at' => now(), 'password' => bcrypt(str()->random(12)), 'status' => 'pending_approval', // Requires manual approval 'cuisine_type' => 'not_specified', 'description' => 'Restaurant registered via social authentication.', ]); // Send welcome email with setup instructions Mail::to($restaurant)->send(new RestaurantWelcomeEmail($restaurant)); // Notify admins of new restaurant registration $this->notifyAdminsOfNewRestaurant($restaurant); return $restaurant; } /** * Update restaurant data on login */ protected function updateRestaurantUser($restaurant, $socialiteUser) { $restaurant->update([ 'last_login_at' => now(), 'social_avatar' => $socialiteUser->getAvatar(), ]); // Log the login for security activity() ->causedBy($restaurant) ->log('Restaurant logged in via ' . session('keychain_provider', 'social')); return $restaurant; } /** * Custom hooks */ protected function afterAuthentication(string $provider, $socialiteUser, $token, $user): void { // Track authentication analytics AnalyticsService::track('social_auth', [ 'provider' => $provider, 'guard' => session('keychain_guard'), 'user_type' => get_class($user), 'new_user' => $user->wasRecentlyCreated, ]); // Update user's last login $user->update(['last_login_at' => now()]); } /** * Generate a restaurant name from social data */ private function generateRestaurantName($socialiteUser): string { $baseName = $socialiteUser->getName() ?? 'Restaurant'; $suffix = Restaurant::where('name', 'like', $baseName . '%')->count() + 1; return $suffix > 1 ? "{$baseName} #{$suffix}" : $baseName; } /** * Notify admins of new restaurant registration */ private function notifyAdminsOfNewRestaurant(Restaurant $restaurant): void { $admins = User::where('role', 'admin')->get(); foreach ($admins as $admin) { Mail::to($admin)->send(new NewRestaurantNotification($restaurant)); } } }
4. Multi-Guard Login View
{{-- resources/views/auth/multi-login.blade.php --}} @extends('layouts.app') @section('content') <div class="container"> <div class="row justify-content-center"> <div class="col-md-10"> <div class="card"> <div class="card-header"> <h4>Choose Your Login Type</h4> </div> <div class="card-body"> <div class="row"> <!-- Customer Login --> <div class="col-md-6"> <div class="login-section border rounded p-4"> <div class="text-center mb-3"> <i class="fas fa-user fa-3x text-primary"></i> <h5 class="mt-2">Customer Login</h5> <p class="text-muted">Browse restaurants, place orders, and manage your account</p> </div> <div class="d-grid gap-2"> @if(config('keychain.providers.google')) <a href="{{ route('keychain.web.redirect', 'google') }}" class="btn btn-outline-danger btn-lg"> <i class="fab fa-google me-2"></i> Continue with Google </a> @endif @if(config('keychain.providers.github')) <a href="{{ route('keychain.web.redirect', 'github') }}" class="btn btn-outline-dark btn-lg"> <i class="fab fa-github me-2"></i> Continue with GitHub </a> @endif </div> </div> </div> <!-- Restaurant Owner Login --> <div class="col-md-6"> <div class="login-section border rounded p-4"> <div class="text-center mb-3"> <i class="fas fa-store fa-3x text-success"></i> <h5 class="mt-2">Restaurant Owner</h5> <p class="text-muted">Manage your restaurant, menu, orders, and analytics</p> </div> <div class="d-grid gap-2"> @if(config('keychain.providers.google')) <a href="{{ route('keychain.restaurant.redirect', 'google') }}" class="btn btn-outline-success btn-lg"> <i class="fab fa-google me-2"></i> Restaurant Login - Google </a> @endif @if(config('keychain.providers.github')) <a href="{{ route('keychain.restaurant.redirect', 'github') }}" class="btn btn-outline-secondary btn-lg"> <i class="fab fa-github me-2"></i> Restaurant Login - GitHub </a> @endif </div> <div class="mt-3 text-center"> <small class="text-muted"> <i class="fas fa-info-circle"></i> New restaurants require approval </small> </div> </div> </div> </div> <!-- Help Section --> <div class="mt-4 text-center"> <h6>Need Help?</h6> <p class="text-muted"> <a href="/help/customer-login">Customer Login Help</a> | <a href="/help/restaurant-login">Restaurant Owner Help</a> | <a href="/contact">Contact Support</a> </p> </div> </div> </div> </div> </div> </div> @endsection @push('styles') <style> .login-section { min-height: 300px; transition: all 0.3s ease; } .login-section:hover { box-shadow: 0 4px 15px rgba(0,0,0,0.1); transform: translateY(-2px); } .btn-lg { padding: 12px 20px; font-size: 1.1rem; } </style> @endpush
5. Restaurant Dashboard
{{-- resources/views/restaurant/dashboard.blade.php --}} @extends('layouts.restaurant') @section('content') <div class="container"> <div class="row"> <div class="col-md-12"> <div class="d-flex justify-content-between align-items-center mb-4"> <h2>Restaurant Dashboard</h2> <div class="text-end"> <small class="text-muted">Welcome back, {{ auth('restaurant')->user()->name }}</small> </div> </div> @if(auth('restaurant')->user()->status === 'pending_approval') <div class="alert alert-warning"> <i class="fas fa-clock"></i> Your restaurant is pending approval. You'll receive an email once approved. </div> @elseif(auth('restaurant')->user()->status === 'approved') <div class="alert alert-success"> <i class="fas fa-check"></i> Your restaurant is approved and active! </div> @endif <!-- Connected Social Accounts --> <div class="card mb-4"> <div class="card-header"> <h5><i class="fas fa-link"></i> Connected Social Accounts</h5> </div> <div class="card-body"> @php $socialAccounts = KeyChainHelper::getUserSocialAccounts(); @endphp @if($socialAccounts) <div class="row"> @foreach($socialAccounts as $account) <div class="col-md-4 mb-3"> <div class="d-flex align-items-center"> <img src="{{ $account['avatar'] }}" class="rounded-circle me-3" width="40" height="40"> <div> <h6 class="mb-0">{{ ucfirst($account['provider']) }}</h6> <small class="text-muted">{{ $account['name'] }}</small> @if(!$account['is_valid']) <br><span class="badge bg-warning">Expired</span> @endif </div> </div> </div> @endforeach </div> @else <p class="text-muted">No social accounts connected.</p> @endif </div> </div> <!-- Restaurant Statistics --> <div class="row"> <div class="col-md-3"> <div class="card text-center"> <div class="card-body"> <i class="fas fa-utensils fa-2x text-primary mb-2"></i> <h5>Menu Items</h5> <h3>{{ $menuItemsCount ?? 0 }}</h3> </div> </div> </div> <div class="col-md-3"> <div class="card text-center"> <div class="card-body"> <i class="fas fa-shopping-cart fa-2x text-success mb-2"></i> <h5>Orders Today</h5> <h3>{{ $ordersToday ?? 0 }}</h3> </div> </div> </div> <div class="col-md-3"> <div class="card text-center"> <div class="card-body"> <i class="fas fa-star fa-2x text-warning mb-2"></i> <h5>Average Rating</h5> <h3>{{ $averageRating ?? 'N/A' }}</h3> </div> </div> </div> <div class="col-md-3"> <div class="card text-center"> <div class="card-body"> <i class="fas fa-dollar-sign fa-2x text-info mb-2"></i> <h5>Revenue Today</h5> <h3>${{ $revenueToday ?? 0 }}</h3> </div> </div> </div> </div> </div> </div> </div> @endsection
6. Routes
// routes/web.php use App\Http\Controllers\RestaurantController; use App\Http\Controllers\CustomerController; use Danyseifeddine\Keychain\Http\Helpers\KeyChainHelper; // Multi-guard login page Route::get('/login', function () { return view('auth.multi-login'); })->name('login'); // Customer routes (web guard) Route::middleware(['auth:web'])->prefix('customer')->group(function () { Route::get('/dashboard', [CustomerController::class, 'dashboard'])->name('customer.dashboard'); Route::get('/profile', [CustomerController::class, 'profile'])->name('customer.profile'); }); // Restaurant routes (restaurant guard) Route::middleware(['auth:restaurant'])->prefix('restaurant')->group(function () { Route::get('/dashboard', [RestaurantController::class, 'dashboard'])->name('restaurant.dashboard'); Route::get('/menu', [RestaurantController::class, 'menu'])->name('restaurant.menu'); Route::get('/orders', [RestaurantController::class, 'orders'])->name('restaurant.orders'); Route::get('/profile', [RestaurantController::class, 'profile'])->name('restaurant.profile'); }); // API endpoints for social account management Route::middleware(['auth:web,restaurant'])->prefix('api')->group(function () { Route::get('/social-accounts', function () { return response()->json([ 'accounts' => KeyChainHelper::getUserSocialAccounts(), 'guard' => KeyChainHelper::getCurrentAuthenticatedGuard(), ]); }); Route::delete('/social-accounts/{provider}', function ($provider) { $user = KeyChainHelper::getCurrentAuthenticatedUser(); $user->unlinkSocialAccount($provider); return response()->json(['message' => 'Account unlinked successfully']); }); });
This example demonstrates a complete multi-restaurant platform with:
- ✅ Separate guards for customers and restaurant owners
- ✅ Custom user creation logic for each guard type
- ✅ Email notifications and approval workflows
- ✅ Professional multi-guard login interface
- ✅ Comprehensive dashboard with social account management
- ✅ API endpoints for account management
- ✅ Security features and activity logging
🔧 Troubleshooting
Common Issues and Solutions
1. Routes Not Working
Problem: KeyChain routes return 404 errors
Solutions:
# Clear route cache php artisan route:clear # Clear config cache php artisan config:clear # Verify routes are registered php artisan route:list --name=keychain
Check configuration:
// config/keychain.php 'routes' => [ 'enabled' => true, // Must be true ],
2. Published Controller Not Being Used
Problem: Custom methods in published controller aren't called
Solution: The package automatically detects and uses the published controller. Verify:
# Check if controller exists ls app/Http/Controllers/Auth/SocialAuthController.php # Clear route cache to pick up new controller php artisan route:clear # Verify routes are using published controller php artisan route:list --name=keychain # Should show: Auth\SocialAuthController instead of Keychain\SocialAuthController
3. Guard Detection Issues
Problem: Wrong guard is detected during authentication
Debug steps:
// Enable debug mode in config/keychain.php 'advanced' => [ 'debug_mode' => true, ], // Check logs at storage/logs/laravel.log for: // "KeyChain Debug - Guard Detection" // "KeyChain Debug - Method Call"
Common causes:
- Session not persisting between redirect and callback
- Route parameters not configured correctly
- Multiple middleware interfering
4. Social Provider Configuration
Problem: Provider redirects fail or show errors
Check credentials:
# Verify environment variables are loaded php artisan tinker >>> config('keychain.services.google') >>> config('services.google') // Should show the same data
Verify OAuth app settings:
- Redirect URIs must match exactly
- Client ID and secret must be correct
- OAuth app must be enabled
5. Database/Migration Issues
Problem: Token storage fails or migration errors
Solutions:
# Re-run migrations php artisan migrate:refresh # Check if table exists php artisan tinker >>> Schema::hasTable('key_chain_tokens') # Verify model relationships >>> $user = User::first(); >>> $user->socialAccounts
6. Token Expiration Issues
Problem: Tokens expire immediately or never expire
Check configuration:
// config/keychain.php 'tokens' => [ 'expiration' => [ 'infinite' => false, // Check this setting 'default_expiry' => 3600, // In seconds ], ], // Debug token creation 'advanced' => [ 'debug_mode' => true, // Check logs for expiration details ],
7. Magic Methods Not Working
Problem: Dynamic methods like createRestaurantUser() not called
Verify configuration:
// config/keychain.php 'advanced' => [ 'dynamic_methods' => true, // Must be true ], 'guards' => [ 'enabled' => true, // Must be true for multi-guard ],
Debug magic methods:
// In your published controller, add debugging: public function __call($method, $parameters) { \Log::info("Magic method called: {$method}", $parameters); return parent::__call($method, $parameters); }
8. Memory/Performance Issues
Problem: High memory usage or slow performance
Optimization steps:
# Clean up expired tokens php artisan keychain cleanup --force # Optimize configuration php artisan config:cache # Check token count php artisan keychain stats
Performance config:
// config/keychain.php 'tokens' => [ 'cleanup_expired' => true, 'cleanup_days' => 7, // Shorter retention ], 'advanced' => [ 'debug_mode' => false, // Always false in production ],
Debug Checklist
When troubleshooting, work through this checklist:
-
✅ Environment Setup
- Composer package installed
- Configuration published
- Migrations run
- Environment variables set
-
✅ Configuration
- Providers enabled in
config/keychain.php - Services configured with credentials
- Guards configured (if using multi-guard)
- Routes enabled
- Providers enabled in
-
✅ OAuth Provider Setup
- OAuth app created in provider console
- Redirect URIs match exactly
- Client ID and secret correct
- OAuth app enabled/published
-
✅ Laravel Setup
- Models have
HasSocialAccountstrait - Guards registered in
auth.php(for multi-guard) - Routes not conflicting
- Middleware working correctly
- Models have
-
✅ Debugging
- Enable debug mode temporarily
- Check Laravel logs
- Verify route registration
- Test with simple single-guard setup first
Getting Help
If you're still experiencing issues:
- Check the logs: Always check
storage/logs/laravel.logwith debug mode enabled - Minimal reproduction: Try with a fresh Laravel install and minimal config
- Community support: Open an issue on GitHub with:
- Laravel version
- KeyChain version
- Complete error message
- Relevant configuration
- Steps to reproduce
📄 Requirements
- PHP: 8.0 or higher
- Laravel: 10.0+ or 11.0+
- Laravel Socialite: 5.0+
- Database: MySQL, PostgreSQL, SQLite, or SQL Server
📜 License
KeyChain is open-sourced software licensed under the MIT license.
🤝 Contributing
Contributions are welcome! Please see our contributing guidelines for details.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
📞 Support
- Documentation: This README covers everything you need
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Email: dany.a.seifeddine@gmail.com
🚀 What's Next?
KeyChain is actively maintained and continuously improved. Upcoming features:
- 🔄 Token refresh automation
- 📱 Mobile OAuth support
- 🎨 More UI components
- 🔐 Advanced security features
- 📊 Enhanced analytics
- 🎯 More social providers
Made with ❤️ by Dany Seifeddine
⭐ If KeyChain helps you, please star the repository!
统计信息
- 总下载量: 387
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 1
- 点击次数: 0
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2025-07-19