Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Social Tokens Laravel Package

pr4w/laravel-social-tokens

Laravel Social Tokens offers a simple way to store, retrieve, and manage social OAuth access tokens for providers like Facebook, Google, and Twitter, with Eloquent models and easy integration into Laravel applications.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish:

composer require pr4w/laravel-social-tokens
php artisan vendor:publish --tag="social-tokens-migrations"   # also check --tag="social-tokens-config"
php artisan migrate

The package ships a social_tokens table (user, provider, provider_id, access/refresh tokens, expires_at) plus a HasSocialTokens trait. It does not perform the OAuth dance — that is still Laravel Socialite's job. This package is the persistence layer for whatever Socialite hands back.

Wire it up:

use Pr4w\LaravelSocialTokens\Traits\HasSocialTokens;

class User extends Authenticatable
{
    use HasSocialTokens;
}

First use case — persist a token in your Socialite callback:

Route::get('/auth/{provider}/callback', function (string $provider) {
    $socialiteUser = Socialite::driver($provider)->user();

    auth()->user()->socialTokens()->updateOrCreate(
        ['provider' => $provider],
        [
            'provider_id'   => $socialiteUser->getId(),
            'access_token'  => $socialiteUser->token,
            'refresh_token' => $socialiteUser->refreshToken,
            'expires_at'    => $socialiteUser->expiresIn
                ? now()->addSeconds($socialiteUser->expiresIn)
                : null,
        ]
    );

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

Where to look first: the published migration (it defines the constraints you will fight later), then the config file for encryption/casts and model swapping, then the trait for the available helpers (hasSocialToken, getSocialToken, the socialTokens relation). Read the README last — the migration is the source of truth.

Implementation Patterns

Upsert-on-login, not create-on-login. A user re-authenticating should overwrite their row, not accumulate duplicates. updateOrCreate keyed on ['provider' => ...] is the canonical pattern; it also refreshes provider_id if the user re-linked a different account.

Single accessor service. Don't sprinkle relation queries through controllers. Wrap token retrieval + refresh in one class so every call site has the same expiry semantics:

class SocialTokenManager
{
    public function tokenFor(User $user, string $provider): string
    {
        $token = $user->getSocialToken($provider); // or ->socialTokens()->where(...)->firstOrFail()

        if ($token->expires_at?->isPast() && $token->refresh_token) {
            $fresh = Socialite::driver($provider)->refreshToken($token->refresh_token);
            $token->update([
                'access_token'  => $fresh->token,
                'refresh_token' => $fresh->refreshToken ?? $token->refresh_token,
                'expires_at'    => $fresh->expiresIn ? now()->addSeconds($fresh->expiresIn) : null,
            ]);
        }

        return $token->access_token;
    }
}

Proactive refresh via a scheduled command. Rather than refreshing inline on every request (adding latency to the hot path), schedule a nightly job that refreshes tokens expiring within 24 hours and marks the rest. Inline refresh stays as a fallback for the case where the job missed.

Multi-provider iteration. Because tokens are keyed per provider, a user can hold GitHub + Google + Slack simultaneously. Any UI that lists "connected accounts" should read the socialTokens collection directly rather than issuing one query per provider.

Decrypt-at-the-edge. Tokens are encrypted at rest by the package's cast. Read the plaintext only in the HTTP client call, never store a decrypted copy on the model or in a DTO that gets cached or serialized into a session.

Testing. Because the cast uses Laravel encryption, tests need a real APP_KEY — RefreshDatabase plus a token factory, and avoid Crypt::setKey() juggling mid-suite. Assert against $token->access_token, not the raw DB column.

Gotchas and Tips

APP_KEY is now load-bearing. Rotating the key silently invalidates every stored token — all users re-authenticate. All queue workers and web nodes must share the key. If you run blue/green deploys, this is a real migration event, not a config tweak.

expires_at is frequently null. GitHub and several flows return no expires_in, so the column stays null. Treat null as "does not expire," not as "expired" — a naive expires_at->isPast() null-guard bug will force re-auth loops. Use ?->isPast() as above.

No refresh token unless you asked for one. Socialite returns refreshToken = null unless you pass offline params (e.g. ->with(['access_type' => 'offline']) for Google). If you skip that, the refresh branch in your manager never fires and users hit a hard expiry wall. Decide this at the login redirect, not later.

Refresh tokens rotate. Google and others return a new refresh token on refresh and invalidate the old one. Always persist $fresh->refreshToken ?? $token->refresh_token — dropping it locks the account out on the next cycle.

Unique index reality check. (user_id, provider) means one account per provider per user; (provider, provider_id) means one row per external account. These are different products ("linked accounts" vs "global identity map"). Check which the migration shipped before you build on it, and handle QueryException on duplicate for concurrent callbacks from two tabs.

Don't let the tokens leak into logs. Avoid ->toArray() on the model in exception handlers or Log::info($token). The cast decrypts on access, so a stray dump is a plaintext credential in your log aggregator — add the token attributes to $hidden on any custom model.

Catch silent discards. If you swap in your own model or add columns, Model::preventSilentlyDiscardingAttributes() in a local environment catches $fillable typos in the updateOrCreate arrays — otherwise a misspelled refresh_token key fails quietly and you only find out at expiry.

Extension points. Publish the migration to add scope, meta JSON, or last_refreshed_at columns; the config supports binding a custom model if you need extra behavior. Prefer subclassing over editing the vendor migration in place, so future upgrades stay clean.

On v3.4.0 specifically: it is a minor bump with no migration or config changes called out — the compare link shows only commit history, so there is no documented breaking surface. Pin ^3.4, run composer update pr4w/laravel-social-tokens, and re-exercise the refresh path plus your callback route in staging: the risky surface for any release in this package is token handling, and that is worth one manual OAuth round-trip rather than trusting CI alone.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox