About us Guides Projects Contacts
Админка
please wait

Introduction

Social authentication simplifies user onboarding by leveraging existing accounts from providers like Google, Facebook, Apple, and GitHub. Users avoid creating another password, and you gain access to verified email addresses. This guide covers implementing OAuth-based social login across major providers, with practical examples using Laravel Socialite.

OAuth Flow Overview

1. User clicks "Sign in with Google"
2. Redirect to Google's authorization URL
3. User authenticates with Google
4. Google redirects back with authorization code
5. Exchange code for access token
6. Fetch user profile with token
7. Create/login user in your system

Laravel Socialite Setup

Installation

composer require laravel/socialite

Configuration

// config/services.php
return [
'google' => [
'client_id' => env('GOOGLE_CLIENT_ID'),
'client_secret' => env('GOOGLE_CLIENT_SECRET'),
'redirect' => env('GOOGLE_REDIRECT_URI'),
],
'facebook' => [
'client_id' => env('FACEBOOK_CLIENT_ID'),
'client_secret' => env('FACEBOOK_CLIENT_SECRET'),
'redirect' => env('FACEBOOK_REDIRECT_URI'),
],
'github' => [
'client_id' => env('GITHUB_CLIENT_ID'),
'client_secret' => env('GITHUB_CLIENT_SECRET'),
'redirect' => env('GITHUB_REDIRECT_URI'),
],
'apple' => [
'client_id' => env('APPLE_CLIENT_ID'),
'client_secret' => env('APPLE_CLIENT_SECRET'),
'redirect' => env('APPLE_REDIRECT_URI'),
],
];

Google Sign-In

Set Up Google OAuth

  1. Go to Google Cloud Console
  2. Create a project
  3. Enable "Google+ API" or "Google Identity Platform"
  4. Go to Credentials → Create OAuth Client ID
  5. Configure authorized redirect URIs: https://yourapp.com/auth/google/callback

Controller Implementation

// routes/web.php
Route::get('/auth/{provider}', [SocialAuthController::class, 'redirect']);
Route::get('/auth/{provider}/callback', [SocialAuthController::class, 'callback']);
// app/Http/Controllers/SocialAuthController.php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Laravel\Socialite\Facades\Socialite;
use Illuminate\Support\Str;
class SocialAuthController extends Controller
{
protected $providers = ['google', 'facebook', 'github', 'apple'];
public function redirect($provider)
{
if (!in_array($provider, $this->providers)) {
abort(404);
}
return Socialite::driver($provider)->redirect();
}
public function callback($provider)
{
if (!in_array($provider, $this->providers)) {
abort(404);
}
try {
$socialUser = Socialite::driver($provider)->user();
} catch (\Exception $e) {
return redirect('/login')->with('error', 'Authentication failed');
}
$user = $this->findOrCreateUser($socialUser, $provider);
Auth::login($user, true);
return redirect()->intended('/dashboard');
}
protected function findOrCreateUser($socialUser, $provider)
{
// Check if the user exists with this provider.
$socialAccount = \App\Models\SocialAccount::where('provider', $provider)
->where('provider_user_id', $socialUser->getId())
->first();
if ($socialAccount) {
return $socialAccount->user;
}
// Check if the user exists with the same email.
$user = User::where('email', $socialUser->getEmail())->first();
if (!$user) {
// Create a new user.
$user = User::create([
'name' => $socialUser->getName(),
'email' => $socialUser->getEmail(),
'email_verified_at' => now(),
'password' => bcrypt(Str::random(24)),
'avatar' => $socialUser->getAvatar(),
]);
}
// Link social account.
$user->socialAccounts()->create([
'provider' => $provider,
'provider_user_id' => $socialUser->getId(),
'token' => $socialUser->token,
'refresh_token' => $socialUser->refreshToken,
]);
return $user;
}
}

Database Setup

// Migration for the social_accounts table.
Schema::create('social_accounts', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->string('provider');
$table->string('provider_user_id');
$table->text('token')->nullable();
$table->text('refresh_token')->nullable();
$table->timestamps();
$table->unique(['provider', 'provider_user_id']);
});
// app/Models/SocialAccount.php
class SocialAccount extends Model
{
protected $fillable = [
'user_id',
'provider',
'provider_user_id',
'token',
'refresh_token',
];
public function user()
{
return $this->belongsTo(User::class);
}
}
// app/Models/User.php
class User extends Authenticatable
{
public function socialAccounts()
{
return $this->hasMany(SocialAccount::class);
}
}

Facebook Login

Set Up Facebook OAuth

  1. Go to Facebook Developers
  2. Create an app
  3. Add Facebook Login product
  4. Configure Valid OAuth Redirect URIs
  5. Get App ID and App Secret from Settings

Additional Scopes

public function redirect($provider)
{
$driver = Socialite::driver($provider);
if ($provider === 'facebook') {
$driver->scopes(['email', 'public_profile']);
}
return $driver->redirect();
}

Apple Sign-In

Set Up Apple OAuth

  1. Go to Apple Developer Console
  2. Create an App ID with Sign in with Apple capability
  3. Create a Services ID
  4. Configure Return URLs
  5. Generate a client secret (requires private key)

Apple-Specific Handling

Apple requires special handling because it only returns user info on first authentication:

public function callback($provider)
{
if ($provider === 'apple') {
return $this->handleAppleCallback();
}
// ... standard handling
}
protected function handleAppleCallback()
{
$socialUser = Socialite::driver('apple')->user();
// Apple only sends the name on first auth.
$name = $socialUser->getName();
if (!$name) {
// Try to get it from previously stored data or request it.
$name = request('user.name.firstName') . ' ' . request('user.name.lastName');
}
// Email might be a private relay.
$email = $socialUser->getEmail();
// ... create/find user
}

GitHub Login

Set Up GitHub OAuth

  1. Go to GitHub Developer Settings
  2. Create an OAuth App
  3. Set Authorization callback URL
  4. Get Client ID and Client Secret

Request Additional Scopes

public function redirect($provider)
{
$driver = Socialite::driver($provider);
if ($provider === 'github') {
$driver->scopes(['user:email', 'read:user']);
}
return $driver->redirect();
}

Frontend Integration

Login Buttons

<div class="social-login">
<a href="/auth/google" class="btn btn-google">
<img src="/icons/google.svg" alt="">
Continue with Google
</a>
<a href="/auth/facebook" class="btn btn-facebook">
<img src="/icons/facebook.svg" alt="">
Continue with Facebook
</a>
<a href="/auth/apple" class="btn btn-apple">
<img src="/icons/apple.svg" alt="">
Continue with Apple
</a>
<a href="/auth/github" class="btn btn-github">
<img src="/icons/github.svg" alt="">
Continue with GitHub
</a>
</div>

Vue.js Component

<template>
<div class="social-auth">
<button
v-for="provider in providers"
:key="provider.name"
@click="authenticate(provider.name)"
:class="`btn-${provider.name}`"
:disabled="loading"
>
<component :is="provider.icon" />
{{ loading ? 'Connecting...' : `Continue with ${provider.label}` }}
</button>
</div>
</template>
<script setup>
import { ref } from 'vue';
const loading = ref(false);
const providers = [
{ name: 'google', label: 'Google' },
{ name: 'facebook', label: 'Facebook' },
{ name: 'apple', label: 'Apple' },
];
function authenticate(provider) {
loading.value = true;
window.location.href = `/auth/${provider}`;
}
</script>

SPA/API Authentication

For single-page applications, use stateless OAuth:

// Return token instead of session.
public function callback($provider)
{
$socialUser = Socialite::driver($provider)->stateless()->user();
$user = $this->findOrCreateUser($socialUser, $provider);
// Create API token.
$token = $user->createToken('social-auth')->plainTextToken;
// Redirect to the frontend with token.
return redirect(config('app.frontend_url') . '/auth/callback?token=' . $token);
}

Security Considerations

State Parameter

Socialite handles CSRF protection with the state parameter automatically. For stateless requests:

// Generate and verify state manually for SPAs.
$state = Str::random(40);
session(['oauth_state' => $state]);
return Socialite::driver($provider)
->with(['state' => $state])
->redirect();

Email Verification

Social providers typically verify emails, but verify the provider's guarantees:

$user = User::create([
'email' => $socialUser->getEmail(),
'email_verified_at' => $this->providerVerifiesEmail($provider)
? now()
: null,
]);
protected function providerVerifiesEmail($provider)
{
return in_array($provider, ['google', 'apple', 'github']);
}

Account Linking

Allow users to link multiple providers:

public function link($provider)
{
// Must be authenticated.
$user = auth()->user();
$socialUser = Socialite::driver($provider)->user();
// Check if already linked to another account.
$existing = SocialAccount::where('provider', $provider)
->where('provider_user_id', $socialUser->getId())
->first();
if ($existing && $existing->user_id !== $user->id) {
return back()->with('error', 'This account is linked to another user');
}
$user->socialAccounts()->updateOrCreate(
['provider' => $provider],
[
'provider_user_id' => $socialUser->getId(),
'token' => $socialUser->token,
]
);
return back()->with('success', 'Account linked successfully');
}

Error Handling

public function callback($provider)
{
try {
$socialUser = Socialite::driver($provider)->user();
} catch (\Laravel\Socialite\Two\InvalidStateException $e) {
return redirect('/login')
->with('error', 'Session expired. Please try again.');
} catch (\GuzzleHttp\Exception\ClientException $e) {
return redirect('/login')
->with('error', 'Authentication was denied.');
} catch (\Exception $e) {
Log::error('Social auth error', [
'provider' => $provider,
'error' => $e->getMessage(),
]);
return redirect('/login')
->with('error', 'Authentication failed. Please try again.');
}
// ... continue with user creation
}

Direct JavaScript SDK Integration

For cases where you need more control or aren't using Laravel Socialite, you can integrate directly with provider SDKs.

Google Sign-In with JavaScript

<div id="g_id_signin"></div>
<script>
const clientId = '<google client ID>'
const backendUrl = '<oauth backend URL>'
var script = document.createElement('script')
script.defer = true
script.async = true
script.src = 'https://accounts.google.com/gsi/client'
script.addEventListener("load", function() {
google.accounts.id.initialize({
client_id: clientId,
callback: (response) => {
if (response?.credential) {
fetch(backendUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token: response.credential }),
})
.then(res => res.json())
.then(data => {
// Process user authentication
localStorage.setItem('auth_token', data.token);
window.location.href = '/dashboard';
})
.catch(err => console.error('Auth failed:', err));
}
}
});
google.accounts.id.renderButton(
document.getElementById('g_id_signin'),
{ theme: 'outline', size: 'large', logo_alignment: 'center' }
);
google.accounts.id.prompt(); // Enable One Tap
});
document.head.appendChild(script);
</script>

Google Token Validation (PHP Backend)

use Illuminate\Support\Facades\Http;
$token = $request->input('token');
// Validate token with Google.
$response = Http::post("https://oauth2.googleapis.com/tokeninfo", [
'id_token' => $token,
]);
if ($response->status() !== 200) {
return response()->json(['error' => 'Invalid token'], 401);
}
$userInfo = $response->json();
// Validate required fields.
if (empty($userInfo['sub']) || empty($userInfo['email'])) {
return response()->json(['error' => 'Missing user info'], 400);
}
$user = User::firstOrCreate(
['email' => $userInfo['email']],
[
'name' => $userInfo['name'],
'email_verified_at' => now(),
'password' => bcrypt(Str::random(24)),
]
);
$token = $user->createToken('auth-token')->plainTextToken;
return response()->json(['token' => $token, 'user' => $user]);

Facebook Login with JavaScript SDK

<div class="fb-login-button"
data-size="large"
data-button-type="login_with"
data-scope="email"
></div>
<div id="fb-root"></div>
<script>
const appId = '<facebook app ID>'
const backendUrl = '<oauth backend URL>'
window.fbAsyncInit = function() {
FB.init({
appId: appId,
cookie: false,
xfbml: true,
version: 'v18.0',
});
FB.Event.subscribe('auth.authResponseChange', function(response) {
if (response?.status === 'connected') {
fetch(backendUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token: response.authResponse.accessToken }),
})
.then(res => res.json())
.then(data => {
localStorage.setItem('auth_token', data.token);
window.location.href = '/dashboard';
});
}
});
FB.AppEvents.logPageView();
};
(function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) return;
js = d.createElement(s); js.id = id;
js.src = 'https://connect.facebook.net/en_US/sdk.js';
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
</script>

Facebook Token Validation (PHP Backend)

$token = $request->input('token');
$response = Http::get("https://graph.facebook.com/me", [
'access_token' => $token,
'fields' => 'id,name,email',
]);
if ($response->status() !== 200) {
return response()->json(['error' => 'Invalid token'], 401);
}
$userInfo = $response->json();
$user = User::firstOrCreate(
['email' => $userInfo['email']],
[
'name' => $userInfo['name'],
'email_verified_at' => now(),
'password' => bcrypt(Str::random(24)),
]
);
// Store Facebook ID for future logins.
$user->socialAccounts()->updateOrCreate(
['provider' => 'facebook'],
['provider_user_id' => $userInfo['id'], 'token' => $token]
);
return response()->json([
'token' => $user->createToken('auth-token')->plainTextToken,
'user' => $user
]);

Apple Sign-In with JavaScript

Apple Sign-In requires special handling, as user info is only provided on first authentication:

<div id="appleid-signin" class="signin-button"
data-color="black" data-border="true" data-type="sign-in"></div>
<script>
const clientId = '<apple service ID identifier>'
const backendUrl = '<oauth backend URL>'
var script = document.createElement('script')
script.src = 'https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js'
script.addEventListener("load", function() {
window.AppleID.auth.init({
clientId: clientId,
scope: 'name email',
redirectURI: window.location.href,
state: 'csrf' + Math.random(),
usePopup: true,
});
document.addEventListener('AppleIDSignInOnSuccess', (event) => {
fetch(backendUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
token: event.detail?.authorization?.id_token,
// Apple only provides name on first auth
name: event.detail?.user?.name?.firstName + ' ' +
event.detail?.user?.name?.lastName,
}),
})
.then(res => res.json())
.then(data => {
localStorage.setItem('auth_token', data.token);
window.location.href = '/dashboard';
});
});
});
document.head.appendChild(script);
</script>

Apple Token Validation (PHP Backend)

use Firebase\JWT\JWK;
use Firebase\JWT\JWT;
$token = $request->input('token');
$fullName = $request->input('name');
$clientId = config('services.apple.client_id');
// Fetch Apple's public keys.
$keysResponse = Http::get("https://appleid.apple.com/auth/keys");
$publicKeys = $keysResponse->json();
try {
$userInfo = (array) JWT::decode($token, JWK::parseKeySet($publicKeys));
} catch (\Exception $e) {
return response()->json(['error' => 'Invalid token'], 401);
}
// Validate token claims.
if ($userInfo['exp'] < time() ||
$userInfo['aud'] !== $clientId ||
$userInfo['iss'] !== 'https://appleid.apple.com') {
return response()->json(['error' => 'Token validation failed'], 401);
}
$user = User::firstOrCreate(
['email' => $userInfo['email']],
[
'name' => $fullName ?: 'Apple User',
'email_verified_at' => now(),
'password' => bcrypt(Str::random(24)),
]
);
return response()->json([
'token' => $user->createToken('auth-token')->plainTextToken,
'user' => $user
]);

Conclusion

Social authentication improves user experience and can increase conversion rates. Implement multiple providers to give users choice, handle edge cases like email conflicts gracefully, and always consider security implications. Whether using Laravel Socialite or direct SDK integration, the patterns in this guide provide a foundation for robust social authentication across major providers.

 
 
 
Языки
Темы
Copyright © 1999 — 2026
ZK Interactive