teltek/pumukit-cas-bundle
Pumukit CAS Bundle adds CAS single sign-on authentication to Pumukit, the open-source Symfony video management platform. It lets users log in through a central CAS server, enabling institutional SSO across services with minimal configuration.
First, the important reality check: this is a Symfony bundle, not a Laravel package. teltek/pumukit-cas-bundle is a thin integration layer that wires the phpCAS client into PuMuKIT — a Symfony-based video management platform. It will not drop into a Laravel composer require and "just work." Treat it as a conceptual reference and a config template, not a dependency.
What it actually gives you is a well-worn pattern for CAS (Central Authentication Service) SSO against a Symfony app. If a Laravel project ever needs CAS — university SSO, enterprise single sign-on, legacy Apereo CAS servers — this repo is a small, readable example of the moving parts:
phpCAS as a service with the CAS host, port, context path, and service URL.uid, mail, displayName) from CAS and map them to a local user provider.Where to look first (in the repo):
DependencyInjection/ — the bundle's config keys (CAS host, port, path, certificate, debug).Security/ — the authenticator/firewall wiring.Resources/config/ — the Symfony service definitions, which translate almost 1:1 into Laravel's config/services.php.First use case for a Laravel dev: SSO-gated video publishing. PuMuKIT = institutional video platform; CAS = "log in with the campus account." The equivalent Laravel job is: gate /admin/videos behind a CAS guard, provision a local User on first login, and use CAS attributes for role assignment.
What you'd actually install instead: apereo/phpcas for the protocol client, and either a community CAS guard package or a ~60-line custom Guard/Authenticatable provider. This bundle is the reference for how to structure that.
The bundle's architecture maps cleanly onto Laravel idioms if you translate the vocabulary.
CAS client as a singleton service. In the bundle, phpCAS::client(...) is called once at bootstrap with config injected via the container. In Laravel:
// config/services.php
'cas' => [
'host' => env('CAS_HOST'),
'port' => env('CAS_PORT', 443),
'context' => env('CAS_CONTEXT', '/cas'),
'service' => env('CAS_SERVICE_URL'), // must match exactly
'cert' => storage_path('cas/cert.pem'),
],
Bind it in a service provider so phpCAS::forceAuthentication() is never called twice per request.
Guard-based authentication, not middleware alone. The bundle uses a Symfony firewall listener. The Laravel analogue is a custom guard:
Auth::extend('cas', function ($app) {
return new CasGuard(
$app['request'],
$app->make(CasClient::class),
$app['auth']->createUserProvider('cas_users'),
);
});
The guard's user() calls phpCAS::checkAuthentication() (redirecting to CAS on forceAuthentication()), then resolves a local User from the uid attribute.
User provisioning on first login. The bundle reads CAS attributes and maps them to the platform's user. Replicate this in a provider or a firstLogin hook:
public function retrieveByCredentials(array $creds) {
$uid = $creds['uid']; // from phpCAS::getAttribute('uid')
return User::firstOrCreate(
['cas_uid' => $uid],
['email' => phpCAS::getAttribute('mail'), 'name' => phpCAS::getAttribute('displayName')],
);
}
Single-logout (SLO) callback. PuMuKIT exposes a route the CAS server can POST to on logout (SAML-style SLO). Add Route::post('/cas/logout', CasLogoutController::class) and remember to Auth::logout() + Session::invalidate() — the bundle does this via a logout listener.
Route protection workflow. Rather than sprinkling Auth::check() everywhere, mirror the firewall concept:
Route::middleware(['web', 'auth:cas'])->group(function () {
Route::get('/admin', AdminController::class);
});
The auth:cas guard triggers the CAS redirect automatically — the same behavior as the bundle's cas firewall.
Integration tip — keep phpCAS behind a wrapper. The bundle's value is that it isolates global-state phpCAS calls behind a service. Do the same: never call phpCAS::* directly in controllers; inject a CasClient interface so you can swap in a mock for tests (phpCAS is hostile to testing).
The service URL is your #1 debugging headache. CAS ticket validation requires the service parameter to match exactly what's registered in the CAS server config — including scheme, trailing slash, and port. The bundle reads this from config and hardcodes it if not set. In Laravel, always drive it from APP_URL and test with the same host you'll use in production (localhost vs 127.0.0.1 mismatches are a classic trap).
Tickets are single-use. phpCAS::forceAuthentication() consumes the ticket from the URL query string. If Laravel's session middleware or a redirect middleware runs before your guard, you can lose the ticket. Ensure the CAS guard runs first in the middleware stack, or intercept the ?ticket= in a dedicated middleware.
phpCAS uses globals and PHP sessions directly. It calls session_start() and stores state in $_SESSION, which fights Laravel's session driver. The bundle lives with this because Symfony and phpCAS both use PHP sessions. In Laravel, you'll typically set session.driver = file (or use a dedicated phpCAS session before Laravel boots) — Redis sessions can split-brain the phpCAS state. Wrap the session and session_write_close() carefully.
TLS certificate validation. Older phpCAS versions default to insecure validation. The bundle's config allows a certificate path; set it. Also set phpCAS::setDebug($logPath) during rollout — you'll need it to see whether validation fails at the CAS server or the client.
DEBUG=true leaks credentials to the log. phpCAS debug logs the service ticket. In production set debug off or log to a non-shared channel. The bundle exposes this via config — don't leave it on.
Attribute naming varies per CAS deployment. The bundle assumes typical Apereo attributes (uid, mail). Real deployments differ (eduPersonPrincipalName, sAMAccountName, custom memberOf for roles). Always verify with phpCAS::getAttributes() against the real server before trusting the mapping.
Forking is the realistic option. With 1 star and 0 dependents, don't expect upstream fixes. The repo is ~200 lines of glue; if you need CAS in Symfony, vendor it into your own src/ and delete the Composer dependency. For Laravel, don't pull it at all — reimplement the ~100 lines as a guard. That is the actual "day-to-day leverage": using this repo as a battle-tested reference, not a dependency.
Extension points worth mirroring: the bundle's Configuration class (typed config tree) and its compiler-pass-style service wiring are what make it swappable. Copy that shape: a CasConfig value object and a factory that returns a CasClient interface. It's the one thing that survives beyond the Symfony world.
How can I help you explore Laravel packages today?