OAuth
Overview#
OAuth authentication is powered by Laravel Socialite, with built-in support for Facebook, Twitter, Google, LinkedIn, GitHub, and Bitbucket.
Need something else? The Socialite Providers GitHub org has over 100 more pre-built providers ready to go.
And if your provider isn't on either list — maybe it's a custom one for your own app — you can roll your own.
Installing Socialite#
Install Socialite with Composer:
composer require laravel/socialite
Enable OAuth in config/statamic/oauth.php or your environment file:
STATAMIC_OAUTH_ENABLED=true
Add the provider to the oauth config so login buttons show up on the CP login form:
'providers' => [
'github',
'apple',
// etc
],
Drop your provider's credentials into config/services.php with a callback URL, as covered in the Socialite docs:
'github' => [
'client_id' => env('GITHUB_CLIENT_ID'),
'client_secret' => env('GITHUB_CLIENT_SECRET'),
'redirect' => 'http://your-site.com/oauth/github/callback',
],
Using a third-party provider? Jump to the steps below.
Usage#
OAuth handles logging in and creating accounts.
Authenticating#
Send users to the provider's login URL to kick off the OAuth flow. Configured providers get buttons on the Control Panel login page automatically — or wire it up on the front-end with the oauth tag:
<a href="{{ oauth:github }}">Log in with GitHub</a>
{{# or with a loop... #}}
{{ oauth }}
<a href="{{ url }}">Log in with {{ label }}</a>
{{ /oauth }}
See the tag docs for more.
Creating accounts#
If someone's logged out and no existing user matches the provider account, a new user gets created on first login. Turn that off with the create_user config option.
Only a subset of data is copied onto the new account — customize that under customizing user data.
Connecting accounts#
Users can connect their account to any configured providers from their Control Panel account area.
On the front-end, build your own connect/disconnect UI with the oauth tags.
Configuration#
Everything lives in config/statamic/oauth.php.
Providers#
List the providers you want so login buttons appear on the CP login page.
You can pass just the provider name, or a name/label pair if you want to customize the display:
'providers' => [
'facebook',
'github' => 'GitHub',
'twitter',
],
If a provider needs "stateless authentication", pass an array with the stateless option:
'providers' => [
'saml2' => ['stateless' => true, 'label' => 'Okta'],
],
One catch: existing accounts can't connect to a stateless OAuth provider. Those are for creating fresh accounts only.
Routes#
Three routes make up the OAuth workflow:
- A login redirect — sends users to the provider's login page
- A callback — where the provider sends them after a successful login
- A disconnect — unlinks a provider from the current user's account
Customize them in config/statamic/oauth.php:
'routes' => [
'login' => 'oauth/{provider}',
'callback' => 'oauth/{provider}/callback',
'disconnect' => 'oauth/{provider}/disconnect',
],
When you create the OAuth app with your provider, you'll need to give them that callback URL.
User flow#
Here's the full flow, plus the config options that steer it:
flowchart TD
Start(["User clicks Log in button"]) --> Provider[Redirected to provider<br/>to authenticate]
Provider --> Callback[Redirected back<br/>to your site]
Callback --> UserConnected{Connected<br/>previously?}
UserConnected -->|Yes| MergeEnabled{Merge user data?}
MergeEnabled -->|Yes| Merge[Update user with<br/>latest data from provider]
MergeEnabled -->|No| LoggedIn
UserConnected -->|No| CreateEnabled{Create new users?}
CreateEnabled -->|Yes| Create[Create a new user]
CreateEnabled -->|No| Denied[Redirect to<br/>unauthorized page]
Create --> LoggedIn
Merge --> LoggedIn([User is logged in])
class LoggedIn ok
class Denied nope
You can customize this flow:
| Option | Description |
|---|---|
create_user |
Whether a new user account should be created when no matching user is found. If false, they'll be redirected to the unauthorized page instead. |
merge_user_data |
Whether an existing user's data should be updated with the latest data from the provider each time they log in. |
unauthorized_redirect |
Where to send someone who's denied access (for example, when create_user is false and no matching user exists). Leave it null to use the Control Panel's unauthorized page when applicable, or fall back to the home page. |
Third party providers#
If Socialite doesn't support your provider natively, use SocialiteProviders.
-
Require the provider with Composer:
composer require socialiteproviders/dropbox -
Register an event listener in your
AppServiceProvider'sbootmethod:// app/Providers/AppServiceProvider.phpEvent::listen(function (\SocialiteProviders\Manager\SocialiteWasCalled $event) {$event->extendSocialite('dropbox', \SocialiteProviders\Dropbox\Provider::class);});Or, if you've got an
EventServiceProvider.php, register it there instead:protected $listen = [\SocialiteProviders\Manager\SocialiteWasCalled::class => ['SocialiteProviders\\Dropbox\\DropboxExtendSocialite@handle',],]; -
Add the credentials to
config/services.php:'dropbox' => ['client_id' => env('DROPBOX_CLIENT_ID'),'client_secret' => env('DROPBOX_CLIENT_SECRET'),'redirect' => 'http://your-site.com/oauth/dropbox/callback',], -
Add the provider to
config/statamic/oauth.php:'providers' => ['dropbox',],
Custom providers#
Provider not in Socialite or SocialiteProviders? Build your own.
You'll need a SocialiteProviders-ready provider — just the event handler (e.g. DropboxExtendSocialite.php) and the provider class (e.g. Dropbox.php).
Follow the third party installation steps, but skip the Composer bits. Keep the classes somewhere in your project and you're good.
Customizing user data#
After authenticating with the provider, the matching user is retrieved — or created if one doesn't exist. Customize that behavior with a callback in your AppServiceProvider.
User data#
Only name is added to the user out of the box. Want more? Return an array from the provider's withUserData callback.
The closure gets:
- an instance of
Laravel\Socialite\Contracts\User - the existing
Statamic\Contracts\Auth\User, if there is one
use Statamic\Facades\OAuth;
OAuth::provider('github')
->withUserData(fn ($socialiteUser, $statamicUser) => [
'name' => $socialiteUser->getName(),
'created_at' => optional($statamicUser)->created_at
?? now()->format('Y-m-d'),
]);
This data gets merged into the user every time they log in with OAuth — including if they already had a non-OAuth account.
Customize entire user creation#
Want full control over the user object being created? Return a user from the provider's withUser callback. The closure gets an instance of Laravel\Socialite\Contracts\User.
use Statamic\Facades\User;
use Statamic\Facades\OAuth;
OAuth::provider('github')->withUser(function ($user) {
return User::make()
->email($user->getEmail())
->set('name', $user->getName());
});
This only runs when the user is first created. To also update data on every login, pair it with withUserData:
public function boot()
{
OAuth::provider('github')
->withUserData(fn ($user) => $this->userData($user))
->withUser(function ($user) {
return User::make()
->email($user->getEmail())
->data($this->userData($user));
});
}
private function userData($user)
{
return [
'name' => $user->getName(),
];
}