承接 danyseifeddine/keychain 相关项目开发

从需求分析到上线部署,全程专人跟进,保证项目质量与交付效率

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

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.

Latest Version on Packagist Total Downloads License

🚀 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

  1. Installation
  2. Basic Configuration
  3. Simple Usage
  4. Multi-Guard Setup
  5. Publishing Assets
  6. Controller Customization
  7. Magic Methods
  8. Token Management
  9. Helper Functions
  10. Commands
  11. Advanced Configuration
  12. Examples
  13. 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

  1. Method Pattern: create{Guard}User() and update{Guard}User()
  2. Automatic Detection: KeyChain detects the guard from the method name
  3. Dynamic Fallback: If method doesn't exist, uses dynamic creation from config
  4. 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:

  1. Install the provider package:
composer require socialiteproviders/discord
  1. 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
],
  1. Add environment variables:
KEYCHAIN_DISCORD_CLIENT_ID=your-discord-client-id
KEYCHAIN_DISCORD_CLIENT_SECRET=your-discord-client-secret
  1. 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:

  1. ✅ Environment Setup

    • Composer package installed
    • Configuration published
    • Migrations run
    • Environment variables set
  2. ✅ Configuration

    • Providers enabled in config/keychain.php
    • Services configured with credentials
    • Guards configured (if using multi-guard)
    • Routes enabled
  3. ✅ OAuth Provider Setup

    • OAuth app created in provider console
    • Redirect URIs match exactly
    • Client ID and secret correct
    • OAuth app enabled/published
  4. ✅ Laravel Setup

    • Models have HasSocialAccounts trait
    • Guards registered in auth.php (for multi-guard)
    • Routes not conflicting
    • Middleware working correctly
  5. ✅ 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:

  1. Check the logs: Always check storage/logs/laravel.log with debug mode enabled
  2. Minimal reproduction: Try with a fresh Laravel install and minimal config
  3. 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.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

📞 Support

🚀 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

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2025-07-19