Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Cas Guard Bundle Laravel Package

l3/cas-guard-bundle

A PHP package that adds a CAS authentication guard, enabling single sign-on against a CAS server. Integrates centralized CAS login into your application with a configurable guard and minimal setup.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a Symfony bundle, not a Laravel package. There is no service provider, no config/cas.php, and no Laravel guard out of the box. A Laravel developer "leveraging" it day-to-day will almost always do so in one of two ways:

  1. Alongside a Laravel app (Symfony sidecar/gateway that authenticates against CAS and hands Laravel a signed token or session), or
  2. As a reference implementation to copy the guard logic into a Laravel custom guard around apereo/phpcas.

If you are evaluating it, look first at these files in the repo (in this order):

  • README.md — the YAML config reference (l3_cas_guard:) and the expected security.yaml firewall wiring.
  • Security/CasAuthenticator.php (or Guard/…) — the actual GuardAuthenticator/AbstractAuthenticator implementation; this is the part you would port.
  • Security/CasUserProvider.php / any CasUser class — how CAS attributes become a user object.
  • DependencyInjection/Configuration.php — the authoritative list of options (host, port, context, cas_version, certificate, server_login_url, server_logout_url, etc.).
  • composer.json — confirm it pulls apereo/phpcas and which Symfony majors it constrains.

Minimal Symfony-side setup (for the sidecar pattern):

composer require l3/cas-guard-bundle
// config/bundles.php
L3\CasGuardBundle\L3CasGuardBundle::class => ['all' => true],
# config/packages/l3_cas_guard.yaml
l3_cas_guard:
    host: cas.example.edu
    port: 443
    context: /cas
    cas_version: "2.0"
    certificate: "%kernel.project_dir%/config/certs/cas.pem"
    # verify option names against Configuration.php — the README is not exhaustive
# config/packages/security.yaml
security:
    providers:
        cas:
            id: l3_cas_guard.user_provider   # check the exact service id
    firewalls:
        main:
            pattern: ^/
            provider: cas
            guard:
                authenticators:
                    - l3_cas_guard.authenticator
            logout:
                path: /logout
                # CAS SLO is configured by the bundle; confirm in CasAuthenticator

First use case to validate end-to-end: protect a single /dashboard route, log in through CAS, dump the returned attributes (phpCAS::getAttributes() inside the authenticator) and confirm you can map uid/mail to your user record. Do this before touching Laravel.

For the Laravel-native path (what you will actually ship), the minimal first step is smaller than the bundle:

composer require apereo/phpcas
// in a service provider boot() or a dedicated CasClient class
\phpCAS::client(CAS_VERSION_2_0, config('cas.host'), (int) config('cas.port'), config('cas.context'));
\phpCAS::setCasServerCACert(config('cas.ca_cert'));   // never use setNoCasServerValidation() in prod

Then wrap that in a custom guard — see the next section.

Implementation Patterns

Pattern A — Symfony sidecar / auth gateway (recommended if you want to use the bundle as-is)

Run a small Symfony app whose only job is CAS. Its firewall uses l3_cas_guard.authenticator. After a successful CAS ticket validation, the authenticator redirects to your Laravel app with either:

  • a short-lived signed JWT containing the CAS attributes, or
  • a one-time code that Laravel exchanges for a session.

Laravel then has a normal session and no knowledge of CAS. This keeps the Symfony dependency isolated and lets you upgrade or replace the bundle without touching Laravel.

// Symfony: in CasAuthenticator::onAuthenticationSuccess()
return new RedirectResponse(
    $this->urlGenerator->generate('laravel_handoff', ['token' => $jwt])
);
// Laravel: routes/web.php
Route::get('/auth/cas/callback', CasHandoffController::class)->name('cas.callback');

Pattern B — Port the authenticator into a Laravel guard

If you want CAS directly in Laravel, don't try to wire the Symfony bundle's DI — copy the behavior of CasAuthenticator into an implementation of Illuminate\Contracts\Auth\Guard (or a middleware).

final class CasGuard implements Guard
{
    public function __construct(private CasClient $cas, private UserProvider $users) {}

    public function user(): ?Authenticatable
    {
        if ($this->user !== null) return $this->user;

        if (! $this->cas->isAuthenticated()) {
            $this->cas->forceAuthentication();   // or ->checkAuthentication() for gateway mode
        }

        $attributes = $this->cas->getAttributes(); // uid, mail, displayName, memberOf, …
        return $this->user = $this->users->retrieveByCasAttributes($attributes);
    }

    public function check(): bool { return $this->user() !== null; }
    public function guest(): bool { return ! $this->check(); }
    public function id() { return $this->user()?->getAuthIdentifier(); }
    public function validate(array $credentials = []): bool { return false; }
    public function hasUser(): bool { return $this->user !== null; }
    public function setUser(Authenticatable $user): static { $this->user = $user; return $this; }
}

Register it in AuthServiceProvider via Auth::extend('cas', …) and use 'driver' => 'cas' in config/auth.php.

Attribute → user mapping

The bundle's user provider does what you'd expect: take the CAS attributes, look up your user by the configured identifier (usually uid), and either fail or provision. In Laravel, make this explicit and idempotent:

$user = User::firstOrCreate(
    ['cas_uid' => $attributes['uid']],
    [
        'name'  => $attributes['displayName'] ?? $attributes['uid'],
        'email' => $attributes['mail'] ?? null,
    ]
);

Store the raw attributes in a cas_attributes JSON column if you will need group/role claims later — CAS attribute names vary wildly between IdPs (memberOf, groups, eduPersonAffiliation).

Logout and single logout (SLO)

The bundle configures a CAS logout redirect on the Symfony side. In Laravel:

public function logout(Request $request)
{
    Auth::logout();
    $request->session()->invalidate();
    $request->session()->regenerateToken();

    return redirect()->away(
        config('cas.logout_url') . '?service=' . urlencode(config('app.url'))
    );
}

If your CAS server supports SLO back-channel, expect it to hit a route on your app; the bundle does not give you a free Laravel route — you must implement one that terminates the Laravel session by cas_uid.

Gateway (lazy) mode for public pages

phpCAS::checkAuthentication() / phpCAS::gateway() will not redirect until the user explicitly opts in. Use this so your landing page stays anonymous but "Sign in" still goes through CAS:

if (! $cas->isAuthenticated() && $request->boolean('login')) {
    $cas->forceAuthentication();
}

The bundle exposes the same distinction — look for checkAuthentication vs forceAuthentication in the authenticator.

Testing

phpCAS is a static singleton and hard to fake. Isolate it behind the CasClient interface above, then in tests bind a FakeCasClient that returns canned attributes. Do not call phpCAS::client() in the test bootstrap.

Gotchas and Tips

  • The Symfony 8 claim is a red flag. The repo advertises Symfony 3–8 support while still shipping a GuardAuthenticator. Guard was deprecated in Symfony 5.3 and removed from the core wiring path in 6.0; "Symfony 8" is not a current release. Pin the version and read composer.json rather than the README.
  • 9 stars, 0 dependents. Treat this as unaudited code. Read CasAuthenticator.php line by line before you put it in an auth path; the opportunity cost of a 200-line port to Laravel is lower than the risk of an unmaintained dependency in your login flow.
  • phpCAS owns the PHP session. phpCAS::client() calls session_start() if no session is active. In Laravel, that means the CAS session and Laravel's session can collide, and you can get "headers already sent" or a session ID mismatch. Start phpCAS before Laravel's StartSession middleware (a service provider register() is the usual spot) or, cleaner, keep CAS in a sidecar so Laravel's session is untouched.
  • Session fixation. After a successful CAS login, regenerate the session ID — in Laravel $request->session()->regenerate(); in Symfony $request->getSession()->migrate(true). Neither the bundle nor phpCAS does this for you by design.
  • Certificate validation is non-negotiable. phpCAS::setNoCasServerValidation() appears in a lot of tutorials and in some bundle examples. Grep your config to make sure it is never reached in production. Use setCasServerCACert($path) with the IdP's CA bundle.
  • Protocol version matters. CAS_VERSION_1_0 returns no attributes; CAS_VERSION_2_0 returns attributes in the ticket validation response; CAS_VERSION_3_0 / SAML_VERSION_1_1 add proxy and attribute semantics. If getAttributes() comes back empty, this is almost always why. Match the cas_version in config to what your IdP actually speaks.
  • Proxy tickets are only issued if the CAS server is configured for them; the bundle's config may accept proxy callbacks but the IdP side is a separate request to your CAS admins. Don't design around proxies without confirming.
  • Config key drift. The README and DependencyInjection/Configuration.php do not always agree, and YAML silently ignores unknown keys under l3_cas_guard: depending on your Symfony config tree. Enable framework.validate / use bin/console config:dump-reference l3_cas_guard to see the real schema.
  • Debugging phpCAS. phpCAS::setDebug('/var/log/cas.log') writes the full ticket-validation request/response. The most common failures are (1) wrong service URL — it must match the service value registered with the CAS server exactly, including scheme and port; (2) clock skew causing INVALID_TICKET; (3) the app being behind a proxy so $_SERVER['SERVER_NAME'] is not the public hostname — set phpCAS::setFixedServiceURL().
  • Extension points in the bundle: the user provider service id can be overridden by defining your own service with the same id in services.yaml; the authenticator can be decorated. That is generally less work than forking the bundle — but if you are already overriding both, port to Laravel.
  • Laravel-specific: do not composer require this bundle into a Laravel project. It will try to pull Symfony security packages you don't use, and its L3CasGuardBundle class will never be registered. Just depend on apereo/phpcas and write the guard.
  • Rate limiting / retry storms. CAS ticket validation is a synchronous HTTP call on every cold login. Behind a load balancer with aggressive health checks or a misconfigured forceAuthentication(), you can slam the IdP. Put a short-lived cache around the "is this session already CAS-authenticated" check and make sure your guard does not re-validate on every request.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment