Installation:
composer require drupol/psrcas
php artisan vendor:publish --provider="Drupol\Psrcas\PsrcasServiceProvider"
This publishes the default config to config/psrcas.php.
Configure CAS Server:
Update config/psrcas.php with your CAS server details:
'server_url' => 'https://your-cas-server.example.com/cas',
'service_url' => 'https://your-app.example.com/cas/callback',
'validation_endpoint' => '/validate',
'login_endpoint' => '/login',
Register Service Provider:
Add the provider to config/app.php under providers:
Drupol\Psrcas\PsrcasServiceProvider::class,
First CAS Validation:
Use the CASClient in a controller or command:
use Drupol\Psrcas\CASClient;
use Illuminate\Support\Facades\Http;
public function validateTicket(Request $request, CASClient $casClient)
{
$ticket = $request->input('ticket');
$response = $casClient->validateTicket($ticket);
if ($response->isSuccess()) {
$user = $this->createOrUpdateUser($response->getAttributes());
auth()->login($user);
return redirect()->intended('/dashboard');
}
return redirect()->route('cas.login')->with('error', 'Invalid ticket');
}
Create a Login Route: Add a route to initiate CAS login:
Route::get('/cas/login', function () {
return redirect()->away(config('psrcas.server_url') . config('psrcas.login_endpoint') .
'?service=' . urlencode(config('psrcas.service_url')));
})->name('cas.login');
Handle Callback: Add a callback route to process the CAS response:
Route::get('/cas/callback', [CasController::class, 'validateTicket']);
login_endpoint.
$casUrl = config('psrcas.server_url') . config('psrcas.login_endpoint') .
'?service=' . urlencode(config('psrcas.service_url'));
return redirect()->away($casUrl);
service_url, validate the ticket using CASClient::validateTicket().
$response = $casClient->validateTicket($request->ticket);
if ($response->isSuccess()) {
$attributes = $response->getAttributes();
// Map attributes to Laravel user and login
}
uid, email) to create/update a Laravel user.
$user = User::firstOrCreate(
['email' => $attributes['email']],
['name' => $attributes['cn'] ?? $attributes['uid']]
);
auth()->login($user);
Create middleware to enforce CAS authentication:
namespace App\Http\Middleware;
use Closure;
use Drupol\Psrcas\CASClient;
use Illuminate\Http\Request;
class EnsureCASAuth
{
public function __construct(protected CASClient $casClient) {}
public function handle(Request $request, Closure $next)
{
if (!$request->has('ticket')) {
return redirect()->route('cas.login');
}
$response = $this->casClient->validateTicket($request->ticket);
if (!$response->isSuccess()) {
return redirect()->route('cas.login')->with('error', 'Invalid ticket');
}
return $next($request);
}
}
Register the middleware in app/Http/Kernel.php:
protected $routeMiddleware = [
'cas.auth' => \App\Http\Middleware\EnsureCASAuth::class,
];
Use it on routes:
Route::middleware(['cas.auth'])->group(function () {
// Protected routes
});
CAS servers return user attributes as key-value pairs. Map these to Laravel users:
public function handleCasAttributes(array $attributes)
{
return [
'email' => $attributes['email'] ?? $attributes['mail'] ?? null,
'name' => $attributes['cn'] ?? $attributes['uid'] ?? null,
'institution' => $attributes['institution'] ?? null,
];
}
Use this in your validation logic:
$userData = $this->handleCasAttributes($response->getAttributes());
$user = User::updateOrCreate(
['email' => $userData['email']],
$userData
);
Store CAS-specific data in the session (e.g., ticket, attributes) for later use:
$request->session()->put('cas.attributes', $response->getAttributes());
$request->session()->put('cas.ticket', $request->ticket);
Retrieve later in middleware or controllers:
$attributes = $request->session()->get('cas.attributes');
Use Laravel’s logging to track CAS events:
\Log::info('CAS validation started', [
'ticket' => $ticket,
'server' => config('psrcas.server_url'),
]);
try {
$response = $casClient->validateTicket($ticket);
\Log::debug('CAS response', ['success' => $response->isSuccess()]);
} catch (\Exception $e) {
\Log::error('CAS validation failed', ['error' => $e->getMessage()]);
}
Service Container Binding:
Bind the CASClient to Laravel’s container for dependency injection:
$this->app->singleton(CASClient::class, function ($app) {
return new CASClient(
$app['config']['psrcas.server_url'],
$app['config']['psrcas.service_url']
);
});
Custom Facade: Create a facade for cleaner syntax:
namespace App\Facades;
use Illuminate\Support\Facades\Facade;
class Psrcas extends Facade
{
protected static function getFacadeAccessor()
{
return 'cas.client';
}
}
Register the facade in config/app.php:
'aliases' => [
'Psrcas' => App\Facades\Psrcas::class,
],
Bind the client in a service provider:
$this->app->bind('cas.client', function ($app) {
return new CASClient(
$app['config']['psrcas.server_url'],
$app['config']['psrcas.service_url']
);
});
Now use Psrcas::validateTicket($ticket) in your code.
Events for CAS Flow: Dispatch events for key CAS actions (e.g., login, validation failure):
event(new \App\Events\CasLoginAttempt($ticket, $response->isSuccess()));
Listen to events in EventServiceProvider:
protected $listen = [
\App\Events\CasLoginAttempt::class => [
\App\Listeners\LogCasAttempt::class,
],
];
Testing with Laravel Dusk/Pest: Test CAS flows using Laravel’s testing tools. Example with Pest:
public function test_cas_login_flow()
{
$this->get('/cas/login')
->assertRedirect(config('psrcas.server_url') . config('psrcas.login_endpoint'));
// Mock CAS response and test callback
$this->actingAs(User::factory()->create())
->get('/cas/callback?ticket=ST-123-ABC')
->assertRedirect('/dashboard');
}
Caching Validated Tickets: Cache ticket validation results to reduce CAS server load:
public function validateTicket(string $ticket)
{
$cacheKey = "cas.ticket.{$ticket}";
if (Cache::has($cacheKey)) {
return Cache::get($cacheKey);
}
$response = $this->casClient->validateTicket($ticket);
Cache::put($cacheKey, $response, now()->addMinutes(10));
return $response;
}
validateTicket calls are efficient and avoid blocking operations (e.g., database queries during validation).How can I help you explore Laravel packages today?