Skip to content

Commit

Permalink
Implement social authentication with Google, LinkedIn, and GitHub; up…
Browse files Browse the repository at this point in the history
…date user and provider models, and enhance user avatar handling
  • Loading branch information
abdessamadbettal committed Dec 16, 2024
1 parent 3571646 commit a5ca697
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 10 deletions.
78 changes: 77 additions & 1 deletion app/Http/Controllers/Auth/ProviderController.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,85 @@
namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Models\User;
use App\Models\Provider;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Laravel\Socialite\Facades\Socialite;
use Inertia\Inertia;

class ProviderController extends Controller
{
//
/**
* Redirect to the provider for authentication.
*
* @return \Illuminate\Http\RedirectResponse
*/
public function redirectToProvider(Request $request)
{
$providerValue = $request->provider;

// Validate the provider to ensure it's supported
if (!in_array($providerValue, ['google', 'linkedin', 'github'])) {
return redirect()->route('home')->with('error', 'Unsupported provider.');
}

// Store the previous URL to redirect back after login
session()->put('previous_url', url()->previous());

return Inertia::location(Socialite::driver($providerValue)->redirect()->getTargetUrl());
}

/**
* Handle the provider callback after authentication.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse
*/
public function handleProviderCallback(Request $request)
{
$providerValue = $request->provider;

// Validate the provider to ensure it's supported
if (!in_array($providerValue, ['google', 'linkedin', 'github'])) {
return redirect()->route('home')->with('error', 'Unsupported provider.');
}

$previousUrl = $request->session()->pull('previous_url', route('welcome')); // Use a named route as default
$providerUser = Socialite::driver($providerValue)->user();

// Find or create the user
$user = User::firstOrCreate(
['email' => $providerUser->getEmail()],
[
'name' => $providerUser->getName(),
'first_name' => $providerUser->user['given_name'] ?? null,
'last_name' => $providerUser->user['family_name'] ?? null,
'password' => Hash::make(Str::random(24)), // Generate a random password
'email_verified_at' => now(),
'avatar' => $providerUser->getAvatar(),
]
);

// Update or create the provider
Provider::updateOrCreate(
[
'provider' => $providerValue,
'provider_id' => $providerUser->getId(),
],
[
'user_id' => $user->id,
'provider_token' => $providerUser->token,
]
);

// Log the user in
Auth::login($user);

return redirect($previousUrl)->with('success', 'Logged in successfully.');


}
}
4 changes: 2 additions & 2 deletions app/Http/Resources/UserResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ class UserResource extends JsonResource
*/
public function toArray(Request $request): array
{
$avatarUrl = $this->getFirstMediaUrl('avatar');
$avatarUrl = $this->avatar ?? $this->getFirstMediaUrl('avatar') ?? null;
$avatar = $avatarUrl ? $avatarUrl : 'https://ui-avatars.com/api/?name=' . urlencode($this->name) . '&color=7F9CF5&background=EBF4FF';

return [
'id' => $this->id,
'name' => $this->name,
Expand Down
47 changes: 47 additions & 0 deletions app/Models/Provider.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,51 @@ class Provider extends Model
{
/** @use HasFactory<\Database\Factories\ProviderFactory> */
use HasFactory;

/**
* The attributes that are mass assignable.
*
* @var string[]
*/
protected $fillable = [
'user_id',
'provider',
'provider_id',
'provider_token',
];

/**
* Get the user that owns the provider.
*/
public function user()
{
return $this->belongsTo(User::class);
}


/**
* Find a provider by the given provider and provider ID.
*
* @param string $provider
* @param string $providerId
* @return static|null
*/
public static function findByProvider($provider, $providerId)
{
return static::where('provider', $provider)
->where('provider_id', $providerId)
->first();
}

/**
* Find a provider by the given user ID.
*
* @param int $userId
* @return static|null
*/
public static function findByUserId($userId)
{
return static::where('user_id', $userId)
->first();
}
}
4 changes: 4 additions & 0 deletions app/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ class User extends Authenticatable implements HasMedia, FilamentUser
'name',
'email',
'password',
'first_name',
'last_name',
'email_verified_at',
'avatar',
];

/**
Expand Down
5 changes: 3 additions & 2 deletions resources/js/Components/Layout/Header.vue
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,9 @@ const headerClass = computed(() => {
<template #trigger>
<span class="inline-flex rounded-md">
<button type="button"
class="inline-flex items-center rounded-md border border-transparent bg-white px-3 py-2 text-sm font-medium leading-4 text-gray-500 transition duration-150 ease-in-out hover:text-gray-700 focus:outline-none dark:bg-gray-800 dark:text-gray-400 dark:hover:text-gray-300">
{{ $page.props.auth.user.name }}
class="inline-flex items-center rounded-md border border-transparent bg-white px-2 py-1 text-sm font-medium leading-4 text-gray-500 transition duration-150 ease-in-out hover:text-gray-700 focus:outline-none dark:bg-gray-800 dark:text-gray-400 dark:hover:text-gray-300">
<!-- {{ $page.props.auth.user.data.name }} -->
<img class="w-6 h-6 rounded" :src="$page.props.auth.user.data.avatar" alt="Default avatar">

<svg class="-me-0.5 ms-2 h-4 w-4" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20" fill="currentColor">
Expand Down
9 changes: 5 additions & 4 deletions resources/js/Pages/Auth/Partials/ProvidersAuth.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<script setup>
import { Link } from '@inertiajs/vue3';
</script>

Expand All @@ -9,7 +10,7 @@
</h1> -->
<div class="w-full flex-1">
<div class="flex items-center gap-3 md:gap-2 flex-col md:flex-row">
<button
<Link :href="route('login.provider', { provider: 'google' })"
class="w-full max-w-xs font-bold shadow-sm rounded-lg p-2 bg-indigo-100 text-gray-800 flex items-center justify-center transition-all duration-300 ease-in-out focus:outline-none hover:shadow focus:shadow-sm focus:shadow-outline">
<div class="bg-white p-2 rounded-full">
<svg class="w-4" viewBox="0 0 533.5 544.3">
Expand All @@ -30,9 +31,9 @@
<span class="ml-4 text-sm">
{{ $t('auth.sign_up_with_google') }}
</span>
</button>
</Link>

<button
<Link :href="route('login.provider', { provider: 'github' })"
class="w-full max-w-xs font-bold shadow-sm rounded-lg p-2 bg-indigo-100 text-gray-800 flex items-center justify-center transition-all duration-300 ease-in-out focus:outline-none hover:shadow focus:shadow-sm focus:shadow-outline">
<div class="bg-white p-1 rounded-full">
<svg class="w-6" viewBox="0 0 32 32">
Expand All @@ -43,7 +44,7 @@
<span class="ml-4 text-sm">
{{ $t('auth.sign_up_with_github') }}
</span>
</button>
</Link>
</div>

<div class="my-12 border-b text-center">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ defineProps({
},
});
const user = usePage().props.auth.user;
const user = usePage().props.auth.user.data;
const form = useForm({
name: user.name,
Expand Down

0 comments on commit a5ca697

Please sign in to comment.