1. Go to this page and download the library: Download danyseifeddine/keychain library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
// Redirect to provider
GET /auth/{provider}/redirect
// Handle callback
GET /auth/{provider}/callback
// Unlink account
DELETE /auth/{provider}/unlink
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'
];
}
// 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'
];
}
// 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
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 = ['[email protected]', '[email protected]'];
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;
}
}
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;
}
}
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',
]
);
}
}
// 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
]);
}
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
};
}
}
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 = 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.
use Danyseifeddine\Keychain\Http\Helpers\KeyChainHelper;
// 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'
]
]
*/
// 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
// 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');
// 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' => '[email protected]',
'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";
}
// Clean up expired tokens (returns number of deleted tokens)
$cleanedUp = KeyChainHelper::cleanupExpiredTokens();
echo "Cleaned up {$cleanedUp} expired tokens";
// config/services.php or in a service provider
Event::listen(function (\SocialiteProviders\Manager\SocialiteWasCalled $event) {
$event->extendSocialite('discord', \SocialiteProviders\Discord\Provider::class);
});
// config/keychain.php
'advanced' => [
'dynamic_methods' => true, // Must be true
],
'guards' => [
'enabled' => true, // Must be true for multi-guard
],
// In your published controller, add debugging:
public function __call($method, $parameters)
{
\Log::info("Magic method called: {$method}", $parameters);
return parent::__call($method, $parameters);
}
bash
# 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