evopixel/socialiteproviders-minecraft-evopixel
A Laravel Socialite provider for Minecraft authentication through EvoPixel. Add Minecraft login to your PHP/Laravel app with a dedicated Socialite driver that simplifies integrating EvoPixel Minecraft accounts.
This is a Laravel Socialite driver that runs a web Authorization Code flow with session-bound state and S256 PKCE, so the browser session — not your own code — carries the security-critical bits. Your only job is to register a Web client, keep the secret on the backend, and wire up two routes.
1. Install
composer require socialiteproviders/mcevo
You need PHP 7.4/8.x and socialiteproviders/manager 4.x (pulled in as a dependency; pin it if you already have it).
2. Register the client
// config/services.php
'mcevo' => [
'client_id' => env('MCEVO_CLIENT_ID'),
'client_secret' => env('MCEVO_CLIENT_SECRET'),
'redirect' => env('MCEVO_REDIRECT_URI'), // must match the dashboard exactly
],
3. Hook the driver into Socialite
// app/Providers/EventServiceProvider.php
protected $listen = [
\SocialiteProviders\Manager\SocialiteWasCalled::class => [
\SocialiteProviders\Mcevo\McevoExtendSocialite::class.'@handle',
],
];
4. Two routes and one controller action
Route::get('/auth/mcevo/redirect', fn () => Socialite::driver('mcevo')->redirect());
Route::get('/auth/mcevo/callback', [McevoController::class, 'callback']);
// McevoController::callback
public function callback(Request $request)
{
$mcevo = Socialite::driver('mcevo')->user();
$user = User::updateOrCreate(
['mcevo_id' => $mcevo->getId()],
[
'name' => $mcevo->getNickname(),
'email' => $mcevo->getEmail(),
'minecraft_uuid' => data_get($mcevo->getRaw(), 'profile.id'),
'minecraft_name' => data_get($mcevo->getRaw(), 'profile.name'),
'access_token' => $mcevo->token,
'refresh_token' => $mcevo->refreshToken,
'expires_at' => now()->addSeconds($mcevo->expiresIn),
]
);
Auth::login($user, remember: true);
return redirect()->intended('/dashboard');
}
Where to look first: the README (integration details live entirely there — scopes, the raw profile shape, and the exact key names for the Minecraft mapping). Then vendor/socialiteproviders/mcevo to confirm the getUserByToken / mapUserToObject behaviour before you rely on any raw key.
First useful use case: "Sign in with mcevo" as the primary login for a Laravel web app, persisting the Minecraft UUID/name at login so you can render skins, run /whitelist style checks, or gate a player dashboard without a second API round-trip.
Token persistence with rotation. The provider returns a refresh token that rotates: every successful refresh invalidates the previous one. Always persist the new pair atomically.
public function refreshToken(McevoAccount $account): McevoAccount
{
$response = Http::asForm()->post(config('services.mcevo.token_url'), [
'grant_type' => 'refresh_token',
'refresh_token' => $account->refresh_token,
'client_id' => config('services.mcevo.client_id'),
'client_secret' => config('services.mcevo.client_secret'),
])->throw();
$account->update([
'access_token' => $response['access_token'],
'refresh_token' => $response['refresh_token'] ?? $account->refresh_token,
'expires_at' => now()->addSeconds($response['expires_in']),
]);
return $account;
}
Wrap refreshes in a lock (Cache::lock("mcevo:refresh:{$account->id}")->block(5)) — rotation plus two concurrent requests equals a dead session.
Refresh pre-emptively, not reactively. Add a 60-second buffer and refresh in middleware or a scheduled command rather than waiting for a 401 mid-request.
Revoke on logout. This release supports revocation, so use it — otherwise the refresh token outlives the session.
Http::asForm()->post(config('services.mcevo.revocation_url'), [
'token' => $account->refresh_token,
'token_type_hint' => 'refresh_token',
'client_id' => config('services.mcevo.client_id'),
'client_secret' => config('services.mcevo.client_secret'),
]);
$account->update(['access_token' => null, 'refresh_token' => null]);
Account linking, not account creation, as the default. Match on mcevo_id first; only fall back to email if you trust the provider's email verification. Never auto-link a provider identity to an existing password account on email match alone.
Treat the Minecraft profile as a cached projection. Store UUID + username at login, refresh on each successful login, and re-fetch when you detect a stale username. Don't call the profile endpoint on every page render.
Testing. Fake the HTTP layer rather than the driver where you care about token handling; fake the driver where you care about your controller:
Socialite::shouldReceive('driver->user')->andReturn(
(new \Laravel\Socialite\Two\User)->map([
'id' => 'abc', 'nickname' => 'Steve', 'token' => 't', 'refreshToken' => 'r',
'expiresIn' => 3600, 'profile' => ['id' => 'uuid', 'name' => 'Steve'],
])
);
Extension points. Socialite::extend('mcevo', ...) if you need a derived driver (custom scopes, a different profile mapping). Listen to SocialiteWasCalled in one place and keep the listener thin — that's the only coupling between the package and your app.
state and the PKCE verifier live in the session. The callback will fail with a state mismatch if the session doesn't survive the round trip. Common causes: SESSION_DRIVER=array, cookie sessions across multiple app servers, a cookie domain that doesn't cover the callback host, or SameSite=Strict on a cross-site redirect. Use redis/database sessions in anything with more than one node.->stateless(). It's the one line that silently disables the protection this release was built around.www. php artisan config:cache after changing it; stale config is the usual culprit for "invalid_client".invalid_grant and the user is logged out. Lock, and treat invalid_grant as "force re-authentication", not "retry".TEXT, not VARCHAR(255), and encrypt at rest (encrypted cast) — they're bearer credentials.getRaw() for the Minecraft mapping is the pragmatic path, but confirm field names against the README and assert on them in a test so a provider-side rename fails loudly instead of writing nulls.InvalidArgumentException: Driver [mcevo] is not supported. Use the SocialiteWasCalled listener form above.expires_at → whether the account still has a refresh token (a prior failed rotation leaves it null).Socialite::driver(...)->redirect() cannot run in a job (no session). Token refresh can — pass the model, not the session.How can I help you explore Laravel packages today?