Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions .agents/skills/socialite-development/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
---
name: socialite-development
description: "Manages OAuth social authentication with Laravel Socialite. Activate when adding social login providers; configuring OAuth redirect/callback flows; retrieving authenticated user details; customizing scopes or parameters; setting up community providers; testing with Socialite fakes; or when the user mentions social login, OAuth, Socialite, or third-party authentication."
license: MIT
metadata:
author: laravel
---

# Socialite Authentication

## Documentation

Use `search-docs` for detailed Socialite patterns and documentation (installation, configuration, routing, callbacks, testing, scopes, stateless auth).

## Available Providers

Built-in: `facebook`, `twitter`, `twitter-oauth-2`, `linkedin`, `linkedin-openid`, `google`, `github`, `gitlab`, `bitbucket`, `slack`, `slack-openid`, `twitch`

Community: 150+ additional providers at [socialiteproviders.com](https://socialiteproviders.com). For provider-specific setup, use `WebFetch` on `https://socialiteproviders.com/{provider-name}`.

Configuration key in `config/services.php` must match the driver name exactly — note the hyphenated keys: `twitter-oauth-2`, `linkedin-openid`, `slack-openid`.

Twitter/X: Use `twitter-oauth-2` (OAuth 2.0) for new projects. The legacy `twitter` driver is OAuth 1.0. Driver names remain unchanged despite the platform rebrand.

Community providers differ from built-in providers in the following ways:
- Installed via `composer require socialiteproviders/{name}`
- Must register via event listener — NOT auto-discovered like built-in providers
- Use `search-docs` for the registration pattern

## Adding a Provider

### 1. Configure the provider

Add the provider's `client_id`, `client_secret`, and `redirect` to `config/services.php`. The config key must match the driver name exactly.

### 2. Create redirect and callback routes

Two routes are needed: one that calls `Socialite::driver('provider')->redirect()` to send the user to the OAuth provider, and one that calls `Socialite::driver('provider')->user()` to receive the callback and retrieve user details.

### 3. Authenticate and store the user

In the callback, use `updateOrCreate` to find or create a user record from the provider's response (`id`, `name`, `email`, `token`, `refreshToken`), then call `Auth::login()`.

### 4. Customize the redirect (optional)

- `scopes()` — merge additional scopes with the provider's defaults
- `setScopes()` — replace all scopes entirely
- `with()` — pass optional parameters (e.g., `['hd' => 'example.com']` for Google)
- `asBotUser()` — Slack only; generates a bot token (`xoxb-`) instead of a user token (`xoxp-`). Must be called before both `redirect()` and `user()`. Only the `token` property will be hydrated on the user object.
- `stateless()` — for API/SPA contexts where session state is not maintained

### 5. Verify

1. Config key matches driver name exactly (check the list above for hyphenated names)
2. `client_id`, `client_secret`, and `redirect` are all present
3. Redirect URL matches what is registered in the provider's OAuth dashboard
4. Callback route handles denied grants (when user declines authorization)

Use `search-docs` for complete code examples of each step.

## Additional Features

Use `search-docs` for usage details on: `enablePKCE()`, `userFromToken($token)`, `userFromTokenAndSecret($token, $secret)` (OAuth 1.0), retrieving user details.

User object: `getId()`, `getName()`, `getEmail()`, `getAvatar()`, `getNickname()`, `token`, `refreshToken`, `expiresIn`, `approvedScopes`

## Testing

Socialite provides `Socialite::fake()` for testing redirects and callbacks. Use `search-docs` for faking redirects, callback user data, custom token properties, and assertion methods.

## Common Pitfalls

- Config key must match driver name exactly — hyphenated drivers need hyphenated keys (`linkedin-openid`, `slack-openid`, `twitter-oauth-2`). Mismatch silently fails.
- Every provider needs `client_id`, `client_secret`, and `redirect` in `config/services.php`. Missing any one causes cryptic errors.
- `scopes()` merges with defaults; `setScopes()` replaces all scopes entirely.
- Missing `stateless()` in API/SPA contexts causes `InvalidStateException`.
- Redirect URL in `config/services.php` must exactly match the provider's OAuth dashboard (including trailing slashes and protocol).
- Do not pass `state`, `response_type`, `client_id`, `redirect_uri`, or `scope` via `with()` — these are reserved.
- Community providers require event listener registration via `SocialiteWasCalled`.
- `user()` throws when the user declines authorization. Always handle denied grants.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
/storage/*.key
/storage/pail
/resources/js/actions
/resources/js/routes
/resources/js/wayfinder
/vendor
.DS_Store
Expand Down
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ This application is a Laravel application and its main Laravel ecosystems packag
- laravel/framework (LARAVEL) - v13
- laravel/prompts (PROMPTS) - v0
- laravel/sanctum (SANCTUM) - v4
- laravel/socialite (SOCIALITE) - v5
- laravel/wayfinder (WAYFINDER) - v0
- laravel/boost (BOOST) - v2
- laravel/mcp (MCP) - v0
Expand Down
59 changes: 59 additions & 0 deletions app/Http/Controllers/Auth/SocialiteController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Models\Social;
use App\Models\User;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Auth;
use Laravel\Socialite\Facades\Socialite;

class SocialiteController extends Controller
{
/**
* Redirect the user to the provider's authentication page.
*/
public function redirect(string $provider): RedirectResponse
{
return Socialite::driver($provider)->redirect();
}

/**
* Obtain the user information from the provider.
*/
public function callback(string $provider): RedirectResponse
{
$socialUser = Socialite::driver($provider)->user();

$socialAccount = Social::where('provider', $provider)
->where('provider_id', $socialUser->getId())
->first();

if ($socialAccount) {
Auth::login($socialAccount->user);

return redirect()->route('dashboard');
}

$user = User::where('email', $socialUser->getEmail())->first();

if (! $user) {
$user = User::create([
'name' => $socialUser->getName() ?? $socialUser->getNickname(),
'email' => $socialUser->getEmail(),
'email_verified_at' => now(),
]);
}

$user->socials()->create([
'provider' => $provider,
'provider_id' => $socialUser->getId(),
'avatar' => $socialUser->getAvatar(),
]);

Auth::login($user);

return redirect()->route('dashboard');
}
}
24 changes: 24 additions & 0 deletions app/Models/Social.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

#[Fillable(['user_id', 'provider', 'provider_id', 'avatar'])]
class Social extends Model
{
use HasFactory;

public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}

public function isProvider(string $provider): bool
{
return $this->provider === $provider;
}
}
16 changes: 16 additions & 0 deletions app/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,22 @@ public function registries(): HasMany
return $this->hasMany(Registry::class);
}

public function hasLinkedProvider(string $provider): bool
{
return $this->socials()->where('provider', $provider)->exists();
}


public function socials(): HasMany
{
return $this->hasMany(Social::class);
}

public function connectedAccounts(): HasMany
{
return $this->socials();
}

/**
* Get the attributes that should be cast.
*
Expand Down
1 change: 1 addition & 0 deletions boost.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"skills": [
"fortify-development",
"laravel-best-practices",
"socialite-development",
"wayfinder-development",
"pest-testing",
"inertia-react-development",
Expand Down
2 changes: 2 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@
"require": {
"php": "^8.3",
"inertiajs/inertia-laravel": "^3.0",
"laravel/cashier-paddle": "^2.8",
"laravel/fortify": "^1.34",
"laravel/framework": "^13.7",
"laravel/sanctum": "^4.0",
"laravel/socialite": "^5.27",
"laravel/tinker": "^3.0",
"laravel/wayfinder": "^0.1.14"
},
Expand Down
Loading
Loading