SSO Logic and Architecture
At its core, the SSO system works by centralizing the login process. Instead of each application managing its own user credentials, they all delegate authentication to a single, trusted Identity Provider (IdP). Your applications (Dashboard, Trips, Events, Blog, Forum) become Service Providers (SPs) that trust the IdP.
Core logic
Entities Involved:
- User-Agent: The user’s web browser.
- SP-Forum: The Service Provider, your Flarum application at
HolidayLandmark.com/forum
. - IdP: The central Identity Provider (your custom Laravel application).
- SP-Trips: Another Service Provider, your
EventMie
application atHolidayLandmark.com/trips
.
Phase 1: Initial Login at SP-Forum
Step 1: User Initiates Access to SP-Forum
The user navigates their browser to https://HolidayLandmark.com/forum
.
Step 2: SP-Forum Checks for Local Session and Redirects
- SP-Forum checks for a local session cookie to determine if the user is already authenticated with the forum application.
- Finding no active session, it determines the user must be authenticated.
- SP-Forum constructs an OAuth 2.0 Authorization Request and redirects the user’s browser to the IdP’s
/authorize
endpoint. This request includes parameters likeclient_id
(identifying SP-Forum),redirect_uri
(where to send the user back),response_type=code
, andscope
.
Step 3: IdP Authenticates the User
- The IdP receives the request. It checks for its own session cookie to see if the user is already authenticated with the IdP.
- Since this is the user’s first visit to any application in the ecosystem, there is no active IdP session.
- The IdP displays its login page, prompting the user for their username and password.
Step 4: User Submits Credentials
The user enters their credentials and submits the form to the IdP.
Step 5: IdP Validates Credentials and Establishes Session
- The IdP validates the credentials against its user database.
- Upon successful validation, the IdP creates a central authentication session for the user and sets a session cookie in the user’s browser. This cookie is scoped to the IdP’s domain.
- The IdP generates a single-use authorization code.
Step 6: IdP Redirects Back to SP-Forum
The IdP redirects the user’s browser back to the redirect_uri
provided by SP-Forum in Step 2. The authorization code is included as a query parameter in the URL.
Step 7: SP-Forum Exchanges Code for Tokens
- The user’s browser loads the
redirect_uri
at SP-Forum. - SP-Forum’s backend receives the authorization code.
- It then makes a secure, back-channel (server-to-server) POST request to the IdP’s
/token
endpoint. This request includes theauthorization_code
, itsclient_id
, andclient_secret
.
Step 8: IdP Issues Access and ID Tokens
- The IdP validates the authorization code,
client_id
, andclient_secret
. - If valid, it invalidates the authorization code (to prevent reuse) and returns an access token and optionally an ID token to SP-Forum.
Step 9: SP-Forum Fetches User Info and Creates Local Session
- SP-Forum uses the received access token to make an API call to the IdP’s user info endpoint (e.g.,
/api/user
). - The IdP validates the access token and returns the user’s details (e.g.,
user_id
,email
,name
). - SP-Forum checks its database for a user with the
user_id
from the IdP.- If the user doesn’t exist, it creates a new user record in its
users
table (without a password). - If the user exists, it proceeds.
- If the user doesn’t exist, it creates a new user record in its
- SP-Forum creates a local session for the user, setting its own session cookie.
Result of Phase 1: The user is now successfully logged into HolidayLandmark.com/forum
. The browser holds two important cookies: one for the SP-Forum session and one for the central IdP session.
Phase 2: Seamless Access to SP-Trips
Step 10: User Navigates to SP-Trips
The user opens https://HolidayLandmark.com/trips
in the same browser.
Step 11: SP-Trips Checks for Local Session and Redirects
- SP-Trips checks for its own local session cookie and finds none.
- It constructs its own OAuth 2.0 Authorization Request and redirects the user’s browser to the IdP’s
/authorize
endpoint.
Step 12: IdP Detects Active Session and Skips Login
- The IdP receives the request from SP-Trips.
- Crucially, it checks for the IdP session cookie set in Step 5. It finds an active session.
- Because the user is already authenticated with the IdP, it completely bypasses the login form.
- The IdP immediately generates a new authorization code for SP-Trips and redirects the user’s browser back to the
redirect_uri
specified by SP-Trips. This happens automatically and almost instantly.
Step 13-15: SP-Trips Completes the Login Flow
The process now follows the same final steps as the first login, but for SP-Trips:
- SP-Trips receives the new authorization code.
- It exchanges the code for its own access token via a back-channel call to the IdP.
- It uses the token to fetch user info from the IdP.
- It creates a local user record (if one doesn’t exist) and establishes a local session for the user.
The flow follows the OAuth 2.0 Authorization Code Grant protocol:
- Access Request: A user tries to access a protected page on an SP (e.g., the Flarum forum).
- Redirect to IdP: The SP sees the user is not logged in and redirects them to the IdP’s login page, including a unique
client_id
andredirect_uri
. - User Authentication: The user enters their single set of credentials on the IdP’s login page.
- Authorization Grant: The IdP validates the credentials and redirects the user back to the SP’s specified
redirect_uri
, providing a temporary authorization code. - Token Exchange: The SP’s backend securely communicates with the IdP, exchanging the authorization code, its
client_id
, and itsclient_secret
for a permanent access token. - User Info & Login: The SP uses this access token to request the user’s information (like email and name) from the IdP’s API. Upon receiving the data, the SP creates a local session and logs the user in.
If the user then visits a different SP, that SP will also redirect to the IdP, which, recognizing the existing authenticated session, will immediately send back a new authorization code without requiring the user to log in again.
Note: You can visualize the process as a central hub (the IdP) with spokes connecting to each of your applications (the SPs). All user authentication traffic is routed through this hub.
Part 1: Building the Central Identity Provider (IdP) with Laravel Passport
This is the most critical component. It will be a standalone Laravel application responsible for managing all users and authentication.
Step 1.1: Set Up the IdP Project
Create a new Laravel project. This will serve as your authentication server (e.g., auth.holidaylandmark.com
).
laravel new holidaylandmark-idp
cd holidaylandmark-idp
Configure your .env
file with your database details and APP_URL
.
Step 1.2: Install and Configure Laravel Passport
Passport will transform your Laravel app into a full OAuth2 server.
1. Install via Composer:
composer require laravel/passport
2. Run Migrations: This creates the necessary database tables for clients and tokens.
php artisan migrate
3. Run Passport Installer: This command creates encryption keys and default clients.
php artisan passport:install
4. Configure User Model: Add the HasApiTokens
trait.
File:
app/Models/User.php
php<?php
namespace App\Models;
use Laravel\Passport\HasApiTokens; // <-- Add this
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
use HasApiTokens, Notifiable; // <-- And add this
// ... rest of the model
}
5. Configure AuthServiceProvider: Register Passport’s routes.
File:
app/Providers/AuthServiceProvider.php
php<?php
namespace App\Providers;
use Laravel\Passport\Passport; // <-- Add this
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
// ...
public function boot()
{
$this->registerPolicies();
Passport::routes(); // <-- Add this line
}
}
6. Configure API Auth Guard: Tell Laravel to use Passport for API authentication.
File:
config/auth.php
php'guards' => [
// ...
'api' => [
'driver' => 'passport', // <-- Change 'token' to 'passport'
'provider' => 'users',
],
],
Step 1.3: Create an API Route to Fetch User Data
Service providers will use this endpoint to get user details after getting an access token.
File:
routes/api.php
php<?php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
Route::middleware('auth:api')->get('/user', function (Request $request) {
return $request->user();
});
Step 1.4: Register Each Service Provider as an OAuth Client
For each of your applications, run this command in your IdP’s terminal. You will receive a Client ID and Client Secret for each one—save these credentials securely.
# For the Dashboard
php artisan passport:client --name="Dashboard" --redirect_uri="https://holidaylandmark.com/auth/callback"
# For the Trips App
php artisan passport:client --name="Trips" --redirect_uri="https://holidaylandmark.com/trips/auth/callback"
# For the Events App
php artisan passport:client --name="Events" --redirect_uri="https://holidaylandmark.com/events/auth/callback"
# For the WordPress Blog (using custom code callback)
php artisan passport:client --name="Blog" --redirect_uri="https://holidaylandmark.com/blogs/wp-login.php?action=hl_sso_callback"
# For the Flarum Forum (using custom code callback)
php artisan passport:client --name="Forum" --redirect_uri="https://holidaylandmark.com/forum/auth/holidaylandmark"
Part 2: Configuring the Laravel Service Providers (Dashboard, Trips, Events)
This section applies to your Dashboard, Trips, and Events applications. The steps are identical for each.
Step 2.1: Install Socialite
composer require laravel/socialite
Step 2.2: Add Credentials
File:
config/services.php
php'holidaylandmark_sso' => [
'client_id' => env('HL_SSO_CLIENT_ID'),
'client_secret' => env('HL_SSO_CLIENT_SECRET'),
'redirect' => env('HL_SSO_REDIRECT_URI'),
],
File:
.env
(Use the unique credentials for each SP)
textHL_SSO_CLIENT_ID=your-client-id-for-this-app
HL_SSO_CLIENT_SECRET=your-client-secret-for-this-app
HL_SSO_REDIRECT_URI=https://your-app-domain.com/auth/callback
Step 2.3: Create a Custom Socialite Provider
Since your IdP is not a standard one like Google, you must teach Socialite how to communicate with it.
1. Install the Socialite Providers Manager:
composer require socialiteproviders/manager
2. Create the Provider Class: This class contains the IdP’s URLs.
File:
app/Services/HolidayLandmarkSSOProvider.php
(Create theServices
folder if needed).
php<?php
namespace App\Services;
use SocialiteProviders\Manager\OAuth2\AbstractProvider;
use SocialiteProviders\Manager\OAuth2\User;
class HolidayLandmarkSSOProvider extends AbstractProvider
{
public const IDENTIFIER = 'HOLIDAYLANDMARK_SSO';
protected function getAuthUrl($state) {
return $this->buildAuthUrlFromBase('https://auth.holidaylandmark.com/oauth/authorize', $state);
}
protected function getTokenUrl() {
return 'https://auth.holidaylandmark.com/oauth/token';
}
protected function getUserByToken($token) {
$response = $this->getHttpClient()->get('https://auth.holidaylandmark.com/api/user', [
'headers' => ['Authorization' => 'Bearer ' . $token],
]);
return json_decode($response->getBody(), true);
}
protected function mapUserToObject(array $user) {
return (new User())->setRaw($user)->map([
'id' => $user['id'], 'nickname' => $user['name'],
'name' => $user['name'], 'email' => $user['email'], 'avatar' => null,
]);
}
}
3. Create the Event Listener: This tells Socialite to use your provider.
File:
app/Listeners/HolidayLandmarkSSOExtendSocialite.php
(Create theListeners
folder).
php<?php
namespace App\Listeners;
use SocialiteProviders\Manager\SocialiteWasCalled;
class HolidayLandmarkSSOExtendSocialite {
public function handle(SocialiteWasCalled $socialiteWasCalled) {
$socialiteWasCalled->extendSocialite('holidaylandmark_sso', \App\Services\HolidayLandmarkSSOProvider::class);
}
}
4. Register the Listener:
File:
app/Providers/EventServiceProvider.php
phpprotected $listen = [
\SocialiteProviders\Manager\SocialiteWasCalled::class => [
\App\Listeners\HolidayLandmarkSSOExtendSocialite::class.'@handle',
],
];
Step 2.4: Create the Login and Callback Routes
This handles the user-facing login flow.
File:
routes/web.php
php<?php
use Illuminate\Support\Facades\Route;
use Laravel\Socialite\Facades\Socialite;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
// Redirects user to the IdP
Route::get('/login', function () {
return Socialite::driver('holidaylandmark_sso')->redirect();
})->name('login');
// Handles the callback from the IdP
Route::get('/auth/callback', function () {
$ssoUser = Socialite::driver('holidaylandmark_sso')->user();
// Find or create a user in this application's database
$user = User::updateOrCreate(
['email' => $ssoUser->getEmail()],
[
'name' => $ssoUser->getName(),
'sso_id' => $ssoUser->getId(), // A new column to store the master user ID from IdP
'password' => Illuminate\Support\Facades\Hash::make(Illuminate\Support\Str::random(24))
]
);
// Log the user into this application
Auth::login($user, true); // 'true' for "remember me"
return redirect('/dashboard'); // Redirect to a protected page
});
Part 3: Configuring the WordPress Service Provider (Code-Based)
Here’s how to create your own plugin for full control over the WordPress integration.
- Create Plugin Directory: In your WordPress site, go to
wp-content/plugins/
and create a folder namedholidaylandmark-sso-client
. - Create Plugin File: Inside that folder, create
holidaylandmark-sso-client.php
. - Add the Code:
File:
wp-content/plugins/holidaylandmark-sso-client/holidaylandmark-sso-client.php
php<?php
/**
* Plugin Name: HolidayLandmark SSO Client
* Description: A custom SSO client to connect to the HolidayLandmark Identity Provider.
*/
// --- Configuration: Replace with your actual credentials ---
define('HL_SSO_CLIENT_ID', 'your-wordpress-client-id');
define('HL_SSO_CLIENT_SECRET', 'your-wordpress-client-secret');
define('HL_SSO_REDIRECT_URI', site_url('/wp-login.php?action=hl_sso_callback'));
define('HL_IDP_AUTHORIZE_URL', 'https://auth.holidaylandmark.com/oauth/authorize');
define('HL_IDP_TOKEN_URL', 'https://auth.holidaylandmark.com/oauth/token');
define('HL_IDP_USERINFO_URL', 'https://auth.holidaylandmark.com/api/user');
// Adds the login button to the WordPress login form
add_action('login_form', function () {
if (session_status() === PHP_SESSION_NONE) session_start();
$state = bin2hex(random_bytes(16));
$_SESSION['oauth2_state'] = $state;
$authorization_url = HL_IDP_AUTHORIZE_URL . '?' . http_build_query([
'client_id' => HL_SSO_CLIENT_ID, 'redirect_uri' => HL_SSO_REDIRECT_URI,
'response_type' => 'code', 'scope' => '', 'state' => $state,
]);
echo '<a href="' . esc_url($authorization_url) . '" style="display:block; text-align:center; padding:10px; background-color:#3498db; color:#fff; text-decoration:none; margin:10px 0;">Login with HolidayLandmark</a>';
});
// Handles the callback from the IdP
add_action('login_init', function () {
if (!isset($_GET['action']) || $_GET['action'] !== 'hl_sso_callback') return;
if (session_status() === PHP_SESSION_NONE) session_start();
if (empty($_GET['state']) || !isset($_SESSION['oauth2_state']) || $_GET['state'] !== $_SESSION['oauth2_state']) {
wp_die('Invalid state parameter.');
}
unset($_SESSION['oauth2_state']);
// Exchange code for token
$response = wp_remote_post(HL_IDP_TOKEN_URL, [
'body' => [ 'grant_type' => 'authorization_code', 'client_id' => HL_SSO_CLIENT_ID,
'client_secret' => HL_SSO_CLIENT_SECRET, 'redirect_uri' => HL_SSO_REDIRECT_URI,
'code' => sanitize_text_field($_GET['code']), ],
]);
if (is_wp_error($response)) wp_die('Failed to get access token.');
$token_data = json_decode(wp_remote_retrieve_body($response), true);
if (empty($token_data['access_token'])) wp_die('Access token not found.');
// Fetch user info
$user_info_response = wp_remote_get(HL_IDP_USERINFO_URL, ['headers' => ['Authorization' => 'Bearer ' . $token_data['access_token']]]);
if (is_wp_error($user_info_response)) wp_die('Failed to fetch user info.');
$user_info = json_decode(wp_remote_retrieve_body($user_info_response), true);
if (empty($user_info['email'])) wp_die('User email not found.');
// Find or create user and log them in
$user = get_user_by('email', $user_info['email']);
if (!$user) {
$username = sanitize_user(explode('@', $user_info['email'])[0], true);
$base_username = $username; $i = 1;
while (username_exists($username)) $username = $base_username . $i++;
$user_id = wp_create_user($username, wp_generate_password(20), $user_info['email']);
wp_update_user(['ID' => $user_id, 'display_name' => sanitize_text_field($user_info['name'])]);
$user = get_user_by('id', $user_id);
}
wp_set_current_user($user->ID, $user->user_login);
wp_set_auth_cookie($user->ID, true);
do_action('wp_login', $user->user_login, $user);
wp_redirect(admin_url());
exit;
});
4. Activate Plugin: In your WordPress admin panel, go to “Plugins” and activate the “HolidayLandmark SSO Client” plugin.
Part 4: Configuring the Flarum Service Provider (Code-Based)
This requires creating a Flarum extension. The process is more complex due to Flarum’s architecture.
- Set up Extension Skeleton: Use
flarum-cli
or manually create the extension folder (e.g.,packages/your-vendor/flarum-ext-holidaylandmark-sso
) withcomposer.json
,extend.php
, andjs
directories. - Add Backend Logic: This code handles the entire OAuth2 flow.
File:
extend.php
(in your extension’s root)
php<?php
namespace YourVendor\FlarumHolidayLandmarkSSO;
use Flarum\Extend;
use Psr\Http\Message\ServerRequestInterface;
use GuzzleHttp\Client;
use Flarum\Http\Rememberer;
use Flarum\Http\SessionAuthenticator;
use Flarum\User\User;
use Laminas\Diactoros\Response\RedirectResponse;
return [
(new Extend\Routes('forum'))
->get('/auth/holidaylandmark', 'auth.holidaylandmark', function (ServerRequestInterface $request, SessionAuthenticator $authenticator, Rememberer $rememberer) {
// --- Config (Store these in Flarum's settings DB or config.php) ---
$idp_authorize_url = 'https://auth.holidaylandmark.com/oauth/authorize';
$idp_token_url = 'https://auth.holidaylandmark.com/oauth/token';
$idp_user_url = 'https://auth.holidaylandmark.com/api/user';
$client_id = 'your-flarum-client-id';
$client_secret = 'your-flarum-client-secret';
$redirect_uri = app('flarum.config')['url'] . '/auth/holidaylandmark';
$session = $request->getAttribute('session');
$queryParams = $request->getQueryParams();
if (!isset($queryParams['code'])) { // Step 1: Redirect to IdP
$session->put('oauth2state', ($state = \Illuminate\Support\Str::random(40)));
$query = http_build_query(['client_id' => $client_id, 'redirect_uri' => $redirect_uri,
'response_type' => 'code', 'scope' => '', 'state' => $state]);
return new RedirectResponse($idp_authorize_url . '?' . $query);
}
// Step 2: Handle Callback
if (empty($queryParams['state']) || $queryParams['state'] !== $session->get('oauth2state')) throw new \Exception('Invalid state');
$httpClient = new Client();
$response = $httpClient->post($idp_token_url, [ 'form_params' => [ 'grant_type' => 'authorization_code',
'client_id' => $client_id, 'client_secret' => $client_secret, 'redirect_uri' => $redirect_uri,
'code' => $queryParams['code'] ]]);
$token = json_decode((string) $response->getBody(), true)['access_token'];
$response = $httpClient->get($idp_user_url, ['headers' => ['Authorization' => 'Bearer ' . $token]]);
$ssoUser = json_decode((string) $response->getBody(), true);
$user = User::where('email', $ssoUser['email'])->first();
if (!$user) {
$user = User::register(['username' => $ssoUser['name'], 'email' => $ssoUser['email'], 'password' => \Illuminate\Support\Str::random(20)]);
$user->activate(); $user->save();
}
$authenticator->logIn($session, $user->id);
return $rememberer->remember(new RedirectResponse(app('flarum.config')['url']));
}),
];
3. Add Frontend Login Button:
File:
js/src/forum/index.js
javascriptimport { extend } from 'flarum/common/extend';
import LogInButtons from 'flarum/forum/components/LogInButtons';
import Button from 'flarum/common/components/Button';
app.initializers.add('your-vendor-holidaylandmark-sso', () => {
extend(LogInButtons.prototype, 'items', function (items) {
items.add('holidaylandmark',
<Button className="Button LogInButton--holidaylandmark"
icon="fas fa-id-card" path="/auth/holidaylandmark">
Login with HolidayLandmark
</Button>
);
});
});
4. Build and Activate: You must compile the JavaScript and then enable the extension in the Flarum admin panel.
The Strategy: Central Roles, Local Mapping
- Define a Superset of Roles in the IdP: In your Laravel IdP, you will define roles that are descriptive of their function across the entire ecosystem. This creates a single source of truth for a user’s permissions. Instead of creating roles like
blog_editor
orevents_organizer
, you create more generic roles.For your specific case, you could define these roles in your IdP:Super Admin
: Has administrative rights everywhere.Content Editor
: Can create and manage content (maps to Editor in WordPress, could be a Moderator in Flarum).Event Organizer
: Specific to the Events app.General User
: The default role for all basic users.
- Pass Roles from IdP to SPs: As established in the previous answer, you must modify your IdP’s
/api/user
endpoint to include an array of the user’s assigned roles in the JSON response. - Implement Mapping Logic in Each SP: This is the most important step. Each application’s SSO callback handler will contain logic to map the IdP’s roles to its own internal roles.
Here’s how you would implement this for each of your applications:
1. Events App (holidaylandmark.com/events
)
This Laravel application has the roles user
, organizer
, and admin
. You will map the IdP roles to these local roles within your /auth/callback
route.
Let’s assume your users
table has a role
column.
File: routes/web.php
(in your Events app)
php// ...
Route::get('/auth/callback', function () {
$ssoUser = Socialite::driver('holidaylandmark_sso')->user();
// Get roles from the IdP.
// The key 'roles' depends on your IdP's API response structure.
$idpRoles = array_column($ssoUser->getRaw()['roles'] ?? [], 'name');
// --- Role Mapping Logic ---
$localRole = 'user'; // Default role
if (in_array('Super Admin', $idpRoles)) {
$localRole = 'admin';
} elseif (in_array('Event Organizer', $idpRoles)) {
$localRole = 'organizer';
}
// --- End Mapping Logic ---
// Find or create the user and assign the mapped role
$user = User::updateOrCreate(
['email' => $ssoUser->getEmail()],
[
'name' => $ssoUser->getName(),
'sso_id' => $ssoUser->getId(),
'password' => Illuminate\Support\Facades\Hash::make(Illuminate\Support\Str::random(24)),
'role' => $localRole // Assign the determined local role
]
);
Auth::login($user, true);
return redirect('/dashboard');
});
Result: If a user logs in with the Super Admin
role from the IdP, they are assigned the admin
role in the Events app. If they have the Event Organizer
role, they become an organizer
. Otherwise, they are a regular user
.
2. WordPress Blog (holidaylandmark.com/blog
)
Your WordPress blog has the roles user
(Subscriber), editor
, and admin
(Administrator). You will implement the mapping in your custom SSO plugin.
File: wp-content/plugins/holidaylandmark-sso-client/holidaylandmark-sso-client.php
php// ... inside the add_action('login_init', ...) function, after you fetch user_info
$user_info = json_decode(wp_remote_retrieve_body($user_info_response), true);
$idpRoles = array_column($user_info['roles'] ?? [], 'name');
// --- Role Mapping Logic ---
$role_mapping = [
'Super Admin' => 'administrator',
'Content Editor' => 'editor',
'General User' => 'subscriber'
];
$wp_role = 'subscriber'; // Default role
// Find the highest-priority role the user has that exists in our mapping
if (in_array('Super Admin', $idpRoles)) {
$wp_role = $role_mapping['Super Admin'];
} elseif (in_array('Content Editor', $idpRoles)) {
$wp_role = $role_mapping['Content Editor'];
} elseif (in_array('General User', $idpRoles)) {
$wp_role = $role_mapping['General User'];
}
// --- End Mapping Logic ---
$user = get_user_by('email', $user_info['email']);
if (!$user) {
// (user creation logic from the guide)
$user_id = wp_create_user(...);
$user = get_user_by('id', $user_id);
}
// Update the user's role every time they log in
wp_update_user([
'ID' => $user->ID,
'role' => $wp_role
]);
// ... rest of the login logic
Result: A user with the IdP role Content Editor
is granted the editor
role in WordPress, and Super Admin
gets administrator
.
3. Flarum Forum (holidaylandmark.com/forum
)
Flarum uses Groups for permissions. Let’s assume you’ve created groups in your Flarum admin panel named Administrators
, Moderators
, and the default Members
.
File: extend.php
(in your Flarum SSO extension)
php// ... inside the ->get('/auth/holidaylandmark', ...) route, after you fetch $ssoUser
$ssoUser = json_decode((string) $response->getBody(), true);
$idpRoles = array_column($ssoUser['roles'] ?? [], 'name');
$user = User::where('email', $ssoUser['email'])->first();
if (!$user) {
// (user registration logic)
}
// --- Role Mapping Logic ---
$groupMapping = [
'Super Admin' => 'Administrators',
'Content Editor' => 'Moderators'
// 'General User' maps to the default 'Members' group, which is automatic for new users
];
// Find the IDs of the Flarum groups to assign
$groupNamesToAssign = [];
foreach ($idpRoles as $role) {
if (isset($groupMapping[$role])) {
$groupNamesToAssign[] = $groupMapping[$role];
}
}
$groupIds = \Flarum\Group\Group::whereIn('name_singular', $groupNamesToAssign)->pluck('id')->all();
// Always include the default Member group (ID 3)
if (!in_array(3, $groupIds)) {
$groupIds[] = 3;
}
// Sync the user's groups based on their IdP roles
$user->groups()->sync($groupIds);
$user->save();
// --- End Mapping Logic ---
$authenticator->logIn($session, $user->id);
return $rememberer->remember(new RedirectResponse(app('flarum.config')['url']));
Result: A user with the Content Editor
role in the IdP will be added to the Moderators
group in Flarum upon login, giving them the specific permissions you have configured for that group.