winter / wn-sso-plugin
Adds support for OAuth-based Single Sign On (SSO) to the Winter CMS backend module through the use of Laravel Socialiate.
Fund package maintenance!
wintercms
Open Collective
Installs: 589
Dependents: 1
Suggesters: 0
Security: 0
Stars: 8
Watchers: 4
Forks: 3
Open Issues: 2
Type:winter-plugin
pkg:composer/winter/wn-sso-plugin
Requires
- laravel/socialite: ~5.3
- socialiteproviders/manager: ^4.0
- winter/wn-backend-module: ^1.2.8 || dev-develop
This package is auto-updated.
Last update: 2026-01-17 09:13:34 UTC
README
Adds OAuth-based Single Sign-On (SSO) authentication to the Winter CMS backend using Laravel Socialite. Allow your backend users to authenticate using their existing accounts from providers like Google, GitHub, Microsoft 365, and more.
Features
- 8 Built-in Providers: GitHub, Google, Facebook, GitLab, Bitbucket, LinkedIn, Twitter (OAuth 1.0 & 2.0)
- Extensible: Easy integration with 100+ community Socialite Providers
- Provider Plugins: Install additional providers as separate plugins (e.g., Winter.SSOProviderMicrosoft)
- Event System: Comprehensive hooks for customizing authentication flow
- Security: SSO ID verification, email normalization, IP logging
- User Registration: Optionally create new users via SSO
- Native Auth Control: Disable username/password login, enforce SSO-only
- Audit Logging: Track all SSO authentication attempts
- Flexible Configuration: Environment-based or file-based setup
Installation
Install via Composer:
composer require winter/wn-sso-plugin php artisan migrate
If using a public folder, republish assets:
php artisan winter:mirror
Quick Start
Let's set up GitHub authentication as an example:
1. Create OAuth App on GitHub
- Go to GitHub Developer Settings
- Click "New OAuth App"
- Fill in the details:
- Application name: Your Site Name
- Homepage URL:
https://example.com - Authorization callback URL:
https://example.com/backend/winter/sso/handle/callback/github
- Save and copy your Client ID and Client Secret
2. Configure Environment Variables
Add to your .env file:
GITHUB_CLIENT_ID=your_client_id_here GITHUB_CLIENT_SECRET=your_client_secret_here
3. Enable the Provider
Edit config/winter/sso/config.php (create if it doesn't exist):
<?php return [ 'enabled_providers' => [ 'github', ], ];
4. Test
Visit /backend/auth/signin - you'll see a "Sign in with GitHub" button!
Configuration Reference
Configuration file: config/winter/sso/config.php
Core Settings
<?php return [ /** * List of enabled providers * Only providers in this array will appear on the login page */ 'enabled_providers' => [ 'google', 'github', // Add more providers here ], /** * Disable native username/password authentication * Forces users to authenticate via SSO only * If only one provider is enabled, redirects directly to it */ 'prevent_native_auth' => false, /** * Allow new user registration via SSO * If false, only existing users can login via SSO */ 'allow_registration' => false, /** * Require explicit permission for SSO connections * Users must explicitly allow each provider (requires backend UI - TODO) */ 'require_explicit_permission' => false, /** * Default role for SSO-registered users * Role code to assign to new users (TODO: not yet implemented) */ 'default_role' => null, /** * Provider-specific configuration * Credentials are typically loaded from environment variables */ 'providers' => [ 'github' => [ 'client_id' => env('GITHUB_CLIENT_ID'), 'client_secret' => env('GITHUB_CLIENT_SECRET'), 'guzzle' => [], // HTTP client options ], 'google' => [ 'client_id' => env('GOOGLE_CLIENT_ID'), 'client_secret' => env('GOOGLE_CLIENT_SECRET'), 'guzzle' => [], ], // Add more providers... ], ];
Redirect URL Format
All providers use this URL pattern:
https://example.com/backend/winter/sso/handle/callback/{provider}
Examples:
- GitHub:
/backend/winter/sso/handle/callback/github - Google:
/backend/winter/sso/handle/callback/google - Microsoft:
/backend/winter/sso/handle/callback/microsoft
Built-in Providers
These providers are supported out of the box by Laravel Socialite:
| Provider | Config Key | Setup Guide |
|---|---|---|
| Bitbucket | bitbucket |
Bitbucket OAuth |
facebook |
Facebook Login | |
| GitHub | github |
GitHub OAuth Apps |
| GitLab | gitlab |
GitLab OAuth |
google |
Setup Guide | |
| LinkedIn (OpenID) | linkedin-openid |
LinkedIn OAuth |
| Twitter (OAuth 1.0) | twitter |
Twitter OAuth |
| Twitter (OAuth 2.0) | twitter-oauth-2 |
Twitter OAuth 2.0 |
Adding Additional Providers
Option 1: Provider Plugins (Recommended)
Provider plugins package everything needed for a specific provider:
# Example: Install Microsoft 365 provider
composer require winter/wn-ssoprovidermicrosoft-plugin
Provider plugins automatically:
- Install the Socialite provider package
- Register with Laravel Socialite
- Add configuration options
- Include provider logos and assets
See Creating Provider Plugins for building your own.
Option 2: Direct Socialite Providers
Use any provider from SocialiteProviders.com:
-
Install the provider package:
composer require socialiteproviders/microsoft
-
Register the provider in your plugin's
boot()method:Event::listen(function (\SocialiteProviders\Manager\SocialiteWasCalled $event) { $event->extendSocialite('microsoft', \SocialiteProviders\Microsoft\Provider::class); });
-
Add configuration:
// config/winter/sso/config.php 'enabled_providers' => ['microsoft'], 'providers' => [ 'microsoft' => [ 'client_id' => env('MICROSOFT_CLIENT_ID'), 'client_secret' => env('MICROSOFT_CLIENT_SECRET'), 'tenant' => env('MICROSOFT_TENANT', 'common'), ], ],
-
Add provider logo (optional): Place an SVG at
/plugins/winter/sso/assets/images/providers/microsoft.svg
Events System
The plugin fires events at every stage of the authentication flow, allowing you to customize behavior:
Available Events
Each event is provider-specific. Replace {provider} with your provider name (e.g., google, github).
1. winter.sso.{provider}.authenticating
Fires before OAuth authentication begins. Return false to abort.
Event::listen('winter.sso.google.authenticating', function () { // Check if authentication should proceed if (!some_condition()) { return false; // Aborts authentication } });
2. winter.sso.{provider}.authenticated
Fires after successful OAuth, before user lookup.
Event::listen('winter.sso.google.authenticated', function ($ssoUser) { // $ssoUser is Laravel\Socialite\AbstractUser Log::info('User authenticated via Google', [ 'email' => $ssoUser->getEmail(), 'id' => $ssoUser->getId(), ]); });
3. winter.sso.{provider}.beforeRegister
Fires before creating a new user account. Throw exception to prevent registration.
Event::listen('winter.sso.google.beforeRegister', function ($ssoUser) { // Only allow company email addresses if (!str_ends_with($ssoUser->getEmail(), '@mycompany.com')) { throw new AuthenticationException('Only company emails allowed'); } });
4. winter.sso.{provider}.registered
Fires after new user is created. Populate additional fields here.
Event::listen('winter.sso.google.registered', function ($user, $ssoUser) { // $user is Backend\Models\User // $ssoUser is Laravel\Socialite\AbstractUser $user->fill([ 'first_name' => $ssoUser->user['given_name'] ?? null, 'last_name' => $ssoUser->user['family_name'] ?? null, ]); $user->save(); });
5. winter.sso.{provider}.beforeLogin
Fires before session is created.
Event::listen('winter.sso.google.beforeLogin', function ($user, $ssoUser) { // Perform checks before allowing login if ($user->is_suspended) { throw new AuthenticationException('Account suspended'); } });
6. winter.sso.{provider}.afterLogin
Fires after successful login and session creation.
Event::listen('winter.sso.google.afterLogin', function ($user, $ssoUser) { // Track login Log::info('User logged in via Google', ['user_id' => $user->id]); // Update last login timestamp $user->last_sso_login = now(); $user->save(); });
Accessing SSO Data
User SSO data is stored in the user's metadata:
// Get SSO data for a specific provider $googleId = $user->getSsoValue('google', 'id'); $token = $user->getSsoValue('google', 'token'); // Set SSO data $user->setSsoValues('google', [ 'id' => '12345', 'token' => 'abc123', 'custom_field' => 'value', ]);
Metadata structure: Backend\Models\User::metadata['winter.sso'][$provider][$key]
Security Features
SSO ID Verification
Once a user connects via a provider, their SSO ID is stored. On subsequent logins, the ID must match. This prevents account takeover if someone else registers the same email with a different provider.
// First login: ID stored $user->setSsoValues('google', ['id' => '123456']); // Later login: ID must match if ($ssoUser->getId() !== $user->getSsoValue('google', 'id')) { throw new InvalidSsoIdException(); }
Email Normalization
Emails are normalized to prevent duplicate accounts:
- Gmail: Dots are removed from usernames (
john.doe@gmail.com→johndoe@gmail.com) - All domains: Lowercased
IP Logging
All authentication attempts are logged with:
- Provider used
- Action taken
- User ID
- SSO provider ID
- Email provided
- IP address
- Metadata (remember me, etc.)
View logs: Settings → Logs → SSO Logs
Session Security
The plugin automatically adjusts session.same_site from strict to lax when secure sessions are enabled, ensuring OAuth callbacks work correctly.
Troubleshooting
"The provider X is not enabled"
Cause: Provider not in enabled_providers array.
Solution: Add provider to config:
'enabled_providers' => ['github', 'google'],
"Invalid state"
Cause: Session lost between redirect and callback, or CSRF protection too strict.
Solutions:
- Ensure sessions are working correctly
- Check
session.same_sitesetting (plugin auto-adjusts tolax) - Clear browser cookies and try again
"Email not found"
Cause: User doesn't exist and allow_registration is false.
Solution: Either:
- Create the user account manually in the backend
- Enable registration:
'allow_registration' => true
"Invalid SSO ID"
Cause: User previously connected with a different account from the same provider.
Solution: This is a security feature. The user must use the original account, or an admin must clear the SSO data:
$user->setSsoValues('provider', ['id' => null]);
SSO buttons not appearing
Checklist:
- Provider is in
enabled_providersarray client_idis set in provider config- Environment variables are loaded correctly
- Assets are published (
php artisan winter:mirror)
Provider-specific issues
Check the provider's setup guide in docs/providers/ for common issues.
Advanced Topics
Preventing Native Authentication
Force SSO-only login:
'prevent_native_auth' => true,
This will:
- Hide the username/password form
- Disable the login AJAX handler
- Show only SSO buttons
If only one provider is enabled, users are redirected directly to that provider.
Customizing Button Appearance
Override button configuration per provider:
'providers' => [ 'github' => [ 'client_id' => env('GITHUB_CLIENT_ID'), 'client_secret' => env('GITHUB_CLIENT_SECRET'), 'button' => [ 'label' => 'Custom Button Text', 'view' => 'your.custom.view', // Custom button template ], ], ],
Provider Scopes
Request additional OAuth scopes:
'providers' => [ 'github' => [ 'client_id' => env('GITHUB_CLIENT_ID'), 'client_secret' => env('GITHUB_CLIENT_SECRET'), 'scopes' => ['user:email', 'read:user'], ], ],
HTTP Client Options (Guzzle)
Configure HTTP client for providers behind proxies or with special requirements:
'providers' => [ 'github' => [ 'client_id' => env('GITHUB_CLIENT_ID'), 'client_secret' => env('GITHUB_CLIENT_SECRET'), 'guzzle' => [ 'timeout' => 10, 'proxy' => 'http://proxy.example.com:8080', ], ], ],
Further Documentation
- Architecture Overview - How the plugin works internally
- Creating Provider Plugins - Build custom provider plugins
- Google Setup Guide - Detailed Google OAuth setup
Contributing
Contributions are welcome! Please submit pull requests to the Winter CMS repository.
License
This plugin is licensed under the MIT License.