Skip to content

SAML2 SSO

Kompass supports enterprise Single Sign-On via SAML2. You can configure one or more Identity Providers (IdPs) so users authenticate with their organisation's existing credentials — no separate password required.

Prerequisite

This feature requires the scaler-tech/laravel-saml2 package. Kompass detects it via Features::hasSaml2() and shows the SSO settings panel only when it is installed.

Setup

Step 1. Install dependency

bash
composer require scaler-tech/laravel-saml2

Step 2. Publish the configuration file

bash
php artisan vendor:publish --provider="Slides\Saml2\ServiceProvider"

Step 3. Run migrations

bash
php artisan migrate

Step 4. Handle the login

The package validates the IdP response and fires a SignedIn event, but it does not log anyone in by itself. You have to map the SAML identity to a local user and call Auth::login — without this step SSO appears to "do nothing" after a successful authentication at the IdP.

Create a listener (auto-discovered from app/Listeners):

php
// app/Listeners/SamlLoginListener.php
namespace App\Listeners;

use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Slides\Saml2\Events\SignedIn;

class SamlLoginListener
{
    public function handle(SignedIn $event): void
    {
        $samlUser = $event->getSaml2User();

        // Most IdPs (Google Workspace, Azure AD) send the e-mail as the NameID.
        $email = $samlUser->getUserId();

        if (! is_string($email) || ! filter_var($email, FILTER_VALIDATE_EMAIL)) {
            return;
        }

        // Auto-provision: create the user on first login.
        $user = User::firstOrNew(['email' => $email]);

        if (! $user->exists) {
            $user->name = Str::before($email, '@');
            $user->password = Hash::make(Str::random(40)); // unused – login is via SSO
            $user->email_verified_at = now();
            $user->save();
        }

        Auth::login($user, remember: true);
    }
}

Restrict who may sign in

For "existing users only", drop the firstOrNew block and return when the user does not exist. To restrict by domain, check Str::after($email, '@') against an allow-list before provisioning.

For the login to persist, the SAML routes need the web middleware group (it starts the session). In config/saml2.php:

php
'routesMiddleware' => ['web'],

Because the IdP POSTs to the ACS/SLS routes without a CSRF token, exclude them from CSRF verification. In bootstrap/app.php (Laravel 11+):

php
->withMiddleware(function (Middleware $middleware) {
    $middleware->validateCsrfTokens(except: [
        'saml2/*',
    ]);
})

Laravel 11+ uses auto-discovery

Listeners in app/Listeners are registered automatically. Do not also add the listener to EventServiceProvider::$listen, or it will run twice.

Step 5. Configure environment variables

Set the redirect targets and any optional settings in your .env. These are read by config/saml2.php:

dotenv
# Where users land after login / logout / on error (fall back to "/" if unset)
SAML2_LOGIN_URL="${APP_URL}/admin/dashboard"
SAML2_LOGOUT_URL="${APP_URL}"
SAML2_ERROR_URL="${APP_URL}/login"

# Print the toolkit's error details (defaults to APP_DEBUG)
SAML2_DEBUG=false
VariablePurpose
SAML2_LOGIN_URLRedirect after a successful login (e.g. the admin dashboard)
SAML2_LOGOUT_URLRedirect after logout
SAML2_ERROR_URLRedirect when the assertion fails validation
SAML2_DEBUGSurface the exact SAML error reason — enable only while debugging

Set per environment

.env is not committed. Add these variables on every server (staging, production). Because the URLs derive from APP_URL, make sure APP_URL is the real domain in production.

Configuring an Identity Provider

Go to Settings → SAML2 SSO and click Add Identity Provider. Fill in the fields for your IdP:

FieldDescription
NameDisplay name for this IdP
Entity IDIdP's unique entity identifier (URI)
Login URLIdP SSO endpoint
Logout URLIdP SLO endpoint (optional)
x509 CertificateIdP's public certificate for signature verification
NameID Formatpersistent, transient, email, or unspecified

Importing Metadata

Instead of filling in the fields manually, you can import the IdP configuration directly from its metadata:

  • URL — Paste the metadata URL; Kompass fetches and parses it automatically.
  • XML file — Upload the metadata XML from your IdP's admin panel.
  • Direct paste — Paste the XML directly into the form.

Prefer the metadata URL

Most enterprise IdPs (Okta, Azure AD, Google Workspace) publish a metadata URL in their admin console. Using the URL import keeps your configuration in sync without manual updates.

Service Provider URLs (register these in your IdP)

Your IdP needs to know where to send users and assertions. Each tenant exposes its own endpoints based on its UUID (shown in the SAML2 settings panel). Register the following in the IdP's app configuration:

IdP fieldValue
ACS / Reply / Assertion Consumer Service URLhttps://<your-domain>/saml2/<tenant-uuid>/acs
Entity ID / Audience / Metadata URLhttps://<your-domain>/saml2/<tenant-uuid>/metadata
Single Logout URL (optional)https://<your-domain>/saml2/<tenant-uuid>/sls

Match APP_URL to the real domain

These URLs are built from APP_URL. If you test locally (e.g. https://app.test) the generated ACS URL points there too. Set APP_URL to the production domain before registering the app with your IdP, or the IdP will reject the assertion destination.

Login Button

Add an SSO button to the Kompass login page under Settings → SAML2 SSO → Login Button:

OptionDescription
LabelButton text (e.g. "Sign in with Okta")
IconSVG icon shown next to the label
EnabledToggle the button on or off without deleting the configuration

Multiple Tenants

Each IdP is represented as a tenant. You can create, edit, and delete tenants from the SAML2 settings panel. Kompass supports multiple tenants for organisations that operate more than one IdP — for example, a parent company and an acquired subsidiary each with their own identity infrastructure.

Security

  • Kompass verifies assertions against the configured x509 certificate.
  • Role middleware enforces access control after SSO login completes.
  • Only administrators can manage the SSO configuration.

Troubleshooting

Google: 403. app_not_configured_for_user

Service is not configured for this user.

This error comes from Google, not from Kompass — the SAML hand-off worked, but Google declined to issue an assertion. The custom SAML app exists in the Google Admin console but is not enabled for the account that is signing in.

Fix it in the Google Admin console:

  1. Go to admin.google.comAppsWeb and mobile apps.
  2. Open your custom SAML app (the one whose Entity ID matches your tenant).
  3. Open User access.
  4. Set the service status to ON for everyone, or turn it on for the specific Organisational Unit / group the users belong to.
  5. Save. Changes can take a few minutes to propagate.

Then retry with an account inside your Workspace domain — a personal Gmail account is not part of the organisation and will trigger the same error.

Other common errors

SymptomLikely causeFix
419 Page Expired on the ACS callbackThe saml2/* routes are behind CSRF protectionExclude saml2/* from CSRF verification
Login succeeds at the IdP but the user is not logged inNo listener handles the Slides\Saml2\Events\SignedIn event, or the SAML routes lack the web middleware (no session)Add a SignedIn listener that maps the SAML identity to a local user and calls Auth::login; set routesMiddleware to ['web']
Invalid issuer / Signature validation failedThe IdP Entity ID or x509 certificate does not match the tenantRe-import the IdP metadata so the certificate and issuer are in sync
Invalid destination / assertion rejectedThe ACS URL registered at the IdP does not match the app's APP_URLAlign APP_URL with the domain registered in the IdP

Read the real reason

Validation failures are logged with key saml2.error_detail. Temporarily set SAML2_DEBUG=true (or APP_DEBUG=true) to surface the exact reason returned by the SAML toolkit.

Released under the MIT License.