socialiteproviders/gitlab
GitLab OAuth provider for Laravel Socialite. Adds GitLab as an authentication driver for Socialite, letting users log in with their GitLab account and retrieve their profile. Install via Composer, add your GitLab client ID and secret, and configure the provider in your services config.
Minimum viable setup:
Install the provider (it pulls in socialiteproviders/manager and expects laravel/socialite):
composer require socialiteproviders/gitlab
Register the extension listener. In older Laravel skeletons this lives in app/Providers/EventServiceProvider.php:
protected $listen = [
\SocialiteProviders\Manager\SocialiteWasCalled::class => [
\SocialiteProviders\GitLab\GitLabExtendSocialite::class.'@handle',
],
];
On Laravel 11+ where EventServiceProvider is absent, either create one or add in AppServiceProvider::boot():
Event::listen(
\SocialiteProviders\Manager\SocialiteWasCalled::class,
\SocialiteProviders\GitLab\GitLabExtendSocialite::class
);
Add credentials to config/services.php:
'gitlab' => [
'client_id' => env('GITLAB_CLIENT_ID'),
'client_secret' => env('GITLAB_CLIENT_SECRET'),
'redirect' => env('GITLAB_REDIRECT_URI'),
// 'instance' => env('GITLAB_INSTANCE', 'https://gitlab.com'),
],
Create an OAuth application in GitLab (User Settings → Applications for gitlab.com, or https://<host>/-/profile/applications for self-hosted) with the callback URL matching GITLAB_REDIRECT_URI exactly, and the read_user scope.
First use case: the classic redirect/callback login flow for an internal tool that authenticates against a self-hosted GitLab instance:
Route::get('/auth/gitlab', fn () => Socialite::driver('gitlab')->redirect());
Route::get('/auth/gitlab/callback', function () {
$gl = Socialite::driver('gitlab')->user();
// $gl->id, $gl->nickname, $gl->name, $gl->email, $gl->avatar
});
If you only need to read a GitLab user profile (not "log in"), you can still use the provider's userFromToken() with a personal access token and skip the redirect dance.
Web login (session-based). The canonical controller above; store the GitLab numeric id on your users table (or a social_identities pivot) and key firstOrCreate on it. Never trust email alone — GitLab users can change their primary email.
Self-hosted GitLab. Set the instance key in services.gitlab (e.g. https://gitlab.example.com). The provider appends /oauth/authorize, /oauth/token, and /api/v4/user to this value, so it works for sub-path installations like https://example.com/gitlab too. This is the differentiator versus using Socialite's built-in providers.
Stateless / API clients. For SPAs or mobile where session state isn't practical:
$user = Socialite::driver('gitlab')->stateless()->userFromToken($accessToken);
Send the user to Socialite::driver('gitlab')->stateless()->redirect()->getTargetUrl() and have the client deliver the code back, or handle the token exchange yourself against <instance>/oauth/token.
Scopes. Default is read_user (enough for /api/v4/user). For repository or CI operations, add api, read_repository, read_api, openid, etc.:
Socialite::driver('gitlab')->scopes(['read_user', 'read_api'])->redirect();
GitLab scopes are space-delimited, which matches Socialite's default formatScopes — no override needed.
Mapping and extension. The provider maps id, username → nickname, name, email, avatar_url → avatar. If you need state, confirmed_at, or web_url, subclass Socialiteproviders\GitLab\Provider and override mapUserToObject(), or just call $user->getRaw() and read the extra keys yourself.
Testing. Socialite's user() uses Guzzle directly, so Http::fake() won't intercept it. Mock at the Socialite boundary instead:
Socialite::shouldReceive('driver->user')->andReturn($fakeUser);
For unit tests of your callback logic, that's usually enough. If you need to test the provider itself, inject a Guzzle MockHandler through the guzzle config key.
Config caching. After editing services.php or .env, run php artisan config:clear (and config:cache in production) — a very common source of "invalid_client" confusion.
SocialiteProviders/Providers; PRs here are rejected. File issues/fixes against the monorepo, then the split is re-tagged.socialiteproviders/manager: ^3.0, which conflicts with the manager 4.x line needed by Laravel 9/10/11. Check composer why-not socialiteproviders/gitlab before assuming a bug. Workarounds: require socialiteproviders/manager:^4.0 explicitly, use a maintained fork, or copy the ~80-line provider class into app/Socialite/ and register it with Socialite::extend().instance with a trailing slash produces https://host//oauth/authorize and GitLab will reject or misroute it. Strip trailing slashes in your env (rtrim).Socialite::extend() drops extra config keys. Socialite::buildProvider() only forwards client_id, client_secret, redirect, and guzzle; instance is passed only via the manager's SocialiteWasCalled path (which consults additionalConfigKeys()). If you hand-roll the extend closure for a self-hosted instance, construct the provider yourself or instance will silently fall back to gitlab.com.http vs https and trailing slashes count. Localhost is fine over HTTP; production should be HTTPS./api/v4/user payload's email is the authenticated user's primary email, but it can be missing or unverified for some accounts. Make your users table's email nullable, or fall back to public_email and prompt for confirmation.InvalidStateException usually means the session isn't shared between the redirect and callback (different subdomain, SESSION_DOMAIN misconfigured, or a load balancer stripping cookies). Use stateless() for API-style flows, but then validate the state yourself.'guzzle' => ['verify' => '/path/to/ca.pem'] (avoid false in production). Without this, token exchange fails with a cURL error that looks unrelated to OAuth.refresh_token and refresh manually — the package won't do it for you.api.userFromToken(); project/group access tokens generally do not have access to /api/v4/user. Make sure users generate a user-owned PAT.Socialite::driver('gitlab')->user() failures give little context; log the raw response by temporarily dumping $user->getRaw() or subclassing getUserByToken() to Log::debug() the body and status. The most common failures are: wrong instance, missing read_user scope, redirect URI mismatch, and expired/revoked token.How can I help you explore Laravel packages today?