A Laravel CAS authentication bundle for EU Login and EC applications.
The package provides:
- package login, logout, callback, and proxy callback routes
- a
laravel-casauth guard driver and provider driver - a
cas.authmiddleware alias for route protection - automatic user creation on first successful CAS login
- optional
CAS_MASQUERADEandCAS_DEMO_MODEflows for non-production environments
If you are consuming the current branch directly from this repository:
composer config repositories.laravel-cas vcs https://github.com/ec-doris/laravel-cas
composer require ec-doris/laravel-cas:dev-mainAfter installation, Laravel package discovery will:
- register the package service provider
- register the
laravel-casauth guard and provider drivers - register default PSR-18 / PSR-17 bindings when those interfaces are not already bound
- register the
cas.authmiddleware alias unless disabled - load the built-in CAS routes as a fallback when you have not published them
You still need to:
- configure your CAS environment variables
- add a
laravel-casguard and provider entry toconfig/auth.php - protect your application routes with
cas.author redirect guests toroute('laravel-cas-login')
After installing the package, follow these steps:
-
Publish the package files (recommended):
php artisan cas:install --all
The package can run with its fallback internal routes if you do not publish them, but publishing is better when you want route discovery, route caching visibility, or customization.
-
Update your
.envfile:CAS_URL=https://webgate.ec.europa.eu/cas CAS_REDIRECT_LOGIN_ROUTE=dashboard CAS_REDIRECT_LOGOUT_URL=https://your-app.com/
-
Add auth guards to
config/auth.php:'guards' => [ 'laravel-cas' => [ 'driver' => 'laravel-cas', 'provider' => 'laravel-cas', ], ], 'providers' => [ 'laravel-cas' => [ 'driver' => 'laravel-cas', 'model' => App\Models\User::class, ], ],
-
Protect your routes with the named middleware:
Route::middleware(['web', 'cas.auth'])->group(function () { Route::get('/dashboard', function () { $user = auth('laravel-cas')->user(); return view('dashboard', compact('user')); })->name('dashboard'); });
Do not add
EcDoris\LaravelCas\Middleware\CasAuthenticatorto the globalwebmiddleware group. That intercepts/loginbefore the package login controller runs and causes/loginto redirect back to itself in a loop. -
Ensure your CAS server allows the callback URL:
https://your-app.com/cas/callback
-
Test the flow:
- Visit
/loginto start CAS authentication. - After successful CAS authentication, you will be redirected to the route named in your
CAS_REDIRECT_LOGIN_ROUTEvariable (e.g.,/dashboard).
- Visit
For better control and frontend tool integration (like Ziggy), publish the CAS routes:
# Install everything (recommended for new installations)
php artisan cas:install --all
# Or install specific components
php artisan cas:install --routes
php artisan cas:install --configThis will:
- Publish
routes/laravel-cas.phpwith all CAS routes - Automatically include them in your
routes/web.php - Allow frontend tools like Ziggy to discover the routes
- Give you full control to customize the routes
Alternatively, publish routes manually:
php artisan vendor:publish --tag=laravel-cas-routesThen add this line to your routes/web.php:
// Include Laravel CAS routes
require __DIR__ . '/laravel-cas.php';This package is designed to work with a small configuration surface.
A minimal .env looks like:
CAS_URL=https://webgate.ec.europa.eu/cas
CAS_REDIRECT_LOGIN_ROUTE=dashboard
CAS_REDIRECT_LOGOUT_URL=https://your-app.com/Notes:
CAS_URLdefaults tohttps://webgate.ec.europa.eu/casCAS_REDIRECT_LOGIN_ROUTEdefaults todashboard; if that route does not exist, post-login redirection falls back to/CAS_REDIRECT_LOGOUT_URLshould usually be set explicitly so CAS logout returns to a valid application URLCAS_MASQUERADE,CAS_DEMO_MODE, andCAS_DEMO_LOGIN_URLare development-only settings and should not be enabled in production
The package uses a fixed internal callback route, /cas/callback, to handle ticket validation. You do not need to configure a separate callback route name.
- When a user accesses a protected route, they are redirected to the CAS server.
- The package tells the CAS server to send the user back to
https://your-app.com/cas/callback. - The
cas.authmiddleware on/cas/callbackvalidates the returned CAS ticket. - After successful validation, the user is redirected to the route you specified in
CAS_REDIRECT_LOGIN_ROUTE.
The package registers the laravel-cas guard and provider drivers, but your application still needs to define a guard and provider entry in config/auth.php:
'guards' => [
'laravel-cas' => [
'driver' => 'laravel-cas',
'provider' => 'laravel-cas',
],
],
'providers' => [
'laravel-cas' => [
'driver' => 'laravel-cas',
'model' => App\Models\User::class,
],
],If auth.providers.users.model already points at the correct model, the explicit model key can be omitted.
The package automatically registers the cas.auth middleware. Use it in your routes:
Route::get('/protected', function () {
return 'This route is protected by CAS';
})->middleware('cas.auth');
// Or in route groups
Route::middleware(['cas.auth'])->group(function () {
Route::get('/dashboard', 'DashboardController@index');
Route::get('/profile', 'ProfileController@index');
});Do not add EcDoris\LaravelCas\Middleware\CasAuthenticator to the global web middleware group. If you do, /login is intercepted before LoginController runs, and guests are redirected back to /login, causing a self-redirect loop.
Use one of these patterns instead:
- Apply the named
cas.authmiddleware only to the routes or route groups that should require CAS authentication. - Keep Laravel's normal
authmiddleware and redirect guests toroute('laravel-cas-login').
For Laravel 11+ apps, guest redirection can be configured in bootstrap/app.php:
use Illuminate\Foundation\Configuration\Middleware;
->withMiddleware(function (Middleware $middleware): void {
$middleware->redirectGuestsTo(fn () => route('laravel-cas-login'));
})To customize the configuration, publish the config files:
php artisan vendor:publish --tag=laravel-cas-config
php artisan cas:install --configThis will publish:
config/laravel-cas.php- Complete CAS configuration with documentation
You can control how routes are loaded:
CAS_AUTO_LOAD_ROUTES=false
CAS_AUTO_REGISTER_MIDDLEWARE=falseUse these when:
CAS_AUTO_LOAD_ROUTES=false: you want to disable the fallback internal route loading and rely only on your publishedroutes/laravel-cas.phpCAS_AUTO_REGISTER_MIDDLEWARE=false: you want to register thecas.authalias yourself instead of letting the package do it
After publishing routes, you'll have access to:
/login- CAS login endpoint (named:laravel-cas-login)/logout- CAS logout endpoint (name:laravel-cas-logout)/cas/callback- Internal CAS callback route (name:laravel-cas-callback)/proxy/callback- CAS proxy callback (name:laravel-cas-proxy-callback)
These routes are now:
- ✅ Discoverable by frontend tools (Ziggy, Laravel Echo, etc.)
- ✅ Customizable in your
routes/laravel-cas.phpfile - ✅ Fully integrated with your application routing
On the published route file, /cas/callback is registered with cas.auth. The controller itself is intentionally blank; the middleware performs the ticket validation and redirect.
Since routes are published to your routes directory, frontend tools will automatically detect them:
// With Ziggy
route('laravel-cas-login') // Available!
route('laravel-cas-logout') // Available!
// Routes are now part of your app's route cache
php artisan route:cacheThe default CAS_URL already points to the European Commission CAS endpoint:
// In your .env file
CAS_URL=https://webgate.ec.europa.eu/cas # Default for EU institutionsIf you publish config/laravel-cas.php, you will also see a presets array with reference values for common EU installations. That array is documentation/reference data; the package does not dynamically switch presets based on CAS_INSTITUTION_CODE or other env flags.
If you want to override the default PSR HTTP client or PSR-17 factory bindings, bind your own implementations in your application container:
use Psr\Http\Client\ClientInterface;
use GuzzleHttp\Client;
use loophp\psr17\Psr17Interface;
use Nyholm\Psr7\Factory\Psr17Factory;
use loophp\psr17\Psr17;
public function register(): void
{
$this->app->bind(
ClientInterface::class,
function(Application $app): ClientInterface {
return new Client(['timeout' => 30]);
}
);
$this->app->bind(
Psr17Interface::class,
function(Application $app): Psr17Interface {
$psr17Factory = new Psr17Factory();
return new Psr17(
$psr17Factory,
$psr17Factory,
$psr17Factory,
$psr17Factory,
$psr17Factory,
$psr17Factory
);
}
);
}The package only supplies its default bindings when these interfaces are not already bound.
The package uses the model configured on auth.providers.laravel-cas.model, or falls back to auth.providers.users.model.
On first successful CAS login:
- the user is looked up by email, case-insensitively
- if no matching user exists, a new one is created
nameis built from CASfirstNameandlastName- if
departmentNumber,department_number, ororganisationare fillable on the model, CASdepartmentNumberis copied into them
Current behavior:
emailis required in the CAS attributes for authentication to succeed- separate
firstNameandlastNamecolumns are not persisted by the package - existing users are reused by email and are not resynchronized from CAS attributes on every login
For local development without CAS server access, use masquerade mode to bypass authentication:
CAS_MASQUERADE=[email protected]When enabled in non-production, visiting /login will automatically create or authenticate the user with the specified email and redirect to the configured post-login route. Visiting /logout clears the local session and redirects without calling the upstream CAS logout endpoint.
For demonstrations and testing with a custom login form, use demo mode:
CAS_DEMO_MODE=true
CAS_DEMO_LOGIN_URL=https://demo-eulogin.cnect.euWhen enabled in non-production:
- Visiting
/loginredirects to the demo login form with areturntoparameter - The demo form collects user information and redirects back with a
ticket=DEMO_{json}parameter - The middleware decodes the demo ticket and creates or authenticates the user
The demo ticket JSON payload should contain:
email(required)firstName(optional)lastName(optional)departmentNumber(optional)
Important: Neither masquerade nor demo mode can be used in production environments. The package will throw an exception if APP_ENV=production with these modes enabled.
If upgrading from an older version:
- Remove manual PSR bindings from your
AppServiceProvider - Remove middleware registration from
Kernel.php - The package now handles these automatically
- Update your
.envfile with the new variable names - Optionally publish and customize the new config files
For development or contribution work in this repository:
git clone https://github.com/ec-doris/laravel-cas.git
cd laravel-cas
composer installUse these commands during package development:
composer verify
vendor/bin/phpunit
npm run e2e
npm run e2e:debug
npm run e2e:headedCommand summary:
composer verify: full local verification loop; installs npm dependencies and the local Playwright Chromium browser if they are missing, then runs PHPUnit and Playwrightvendor/bin/phpunit: PHPUnit suite onlynpm run e2e: Playwright suite onlynpm run e2e:debug/npm run e2e:headed: browser debugging variants
The repository includes a local Workbench app, a deterministic CAS protocol stub, and Playwright browser tests so package changes can be exercised without pulling the branch into a separate Laravel application.
Run the full local verification loop:
composer verifycomposer verify is the one-command local validation loop. It will:
- Install npm dependencies if
node_modulesis missing - Install the local Playwright Chromium browser if it is missing
- Run the PHPUnit suite
- Run the Playwright end-to-end suite
What this covers:
- Guest access to a protected Workbench route redirects through
/login /loginredirects to a local CAS server- The CAS server issues a real service ticket and redirects to
/cas/callback - The package validates the ticket, creates the user if needed, authenticates the session, persists mapped attributes, and redirects to the dashboard
/logoutclears the session and round-trips through CAS logout back to the Workbench app
Manual local loop:
npm install
npm run e2e:install
npm run cas:stub
./scripts/e2e/serve-workbench.shThen, in another terminal:
npm run e2eOr open:
http://127.0.0.1:8001/dashboard
This harness uses a local CAS stub, not a real Apereo CAS or EU Login server. It is intended to exercise the package end to end inside this repository.
Useful local commands:
composer verify
npm run cas:stub
./scripts/e2e/serve-workbench.sh
curl -i http://127.0.0.1:8001/login
curl -i http://127.0.0.1:9800/cas/__healthLocal CAS stub credentials:
username: casuser
password: Mellon
The Workbench dashboard exposes name, email, departmentNumber, department_number, and organisation so user creation and attribute-mapping behavior are visible during development.
The GitHub Actions workflow runs the same verification flow on pushes and pull requests, so local and CI coverage stay aligned.