misd/raven-bundle
Symfony bundle that integrates the Raven Sentry client, automatically capturing exceptions and errors and reporting them to Sentry. Provides configurable Symfony service wiring so PHP applications can add Sentry error tracking with minimal setup.
Read this first: the package does not install into Laravel. misd/raven-bundle is a Symfony 2 bundle. It registers through AppKernel, hooks Symfony's DI container and security.firewall, and implements AuthenticationProviderInterface. There is no service provider, no config/auth.php hook, and Composer will refuse resolution on PHP 8 / Laravel 9+ without --ignore-platform-reqs (never do that for auth code). Also note the package description.md in this repo is stale/wrong — it describes a Sentry/Raven error-tracking integration. The actual bundle is Cambridge Ucam WebAuth (Raven) login. The word "Raven" is doing double duty; confirm which one you actually need before spending a day on it.
So "getting started" means: clone it, read it, port ~150 lines of protocol logic into a Laravel guard. Budget a 1–2 day spike.
Minimal steps for the spike:
DependencyInjection/Configuration.php — the authoritative list of config keys (host, port, path, key, timeouts, logger).Security/ — the listener and authentication provider that perform the redirect-out / redirect-in dance and validate the WLS-Response.Raven client wrapper — this is the part you actually want to copy: signature verification, issue freshness check, params replay check.https://raven.cam.ac.uk/auth/authenticate.html for prod, but use the test/demo host for the spike). Capture a real WLS-Response and keep it as a fixture.First use case worth shipping: gate an internal admin/dashboard route behind Cambridge SSO and read the CRSID (principal) off the session, mapping it to a local users row on first login.
If you are in a genuinely legacy Symfony 2.x app, the native install looks like:
// app/AppKernel.php
$bundles = [ /* ... */ new Misd\RavenBundle\MisdRavenBundle() ];
# app/config/config.yml
misd_raven:
host: raven.cam.ac.uk
port: 443
path: /auth/authenticate.html
key: "<session key used to sign the request>"
# app/config/security.yml
security:
firewalls:
secured:
pattern: ^/
misd_raven: ~
access_control:
- { path: ^/, roles: ROLE_USER }
The protocol flow (this is what you are porting). There are only three moving parts:
authenticate.html with ver=3, url (your callback — must be HTTPS, Raven rejects http and non-https callbacks), date (ISO 8601 UTC), desc, and skey. Optionally ptags if you want Raven to assert group membership.url with a WLS-Response query parameter. Decode it and check, in this order:
ver is a version you support and status is 200.sig — recompute the signature over the response fields (excluding sig) using Raven's published key for the kid in the response. Use a constant-time comparison (hash_equals).issue — timestamp must be within a tolerance window (Raven specifies minutes; enforce a small skew tolerance and require NTP).url — must match the callback URL you sent, exactly.params — if you sent a nonce/state in params, it must match your stored value and must not have been seen before (replay protection). This requires a shared store if you run more than one app instance.principal (the CRSID) to a local user.Laravel translation — custom guard + two routes. Do not try to bolt this onto Auth::attempt(); model it as a redirect-based provider:
// routes/web.php
Route::get('/auth/raven', [RavenController::class, 'redirect'])->name('raven.login');
Route::get('/auth/raven/callback', [RavenController::class, 'callback'])->name('raven.callback');
// app/Http/Controllers/RavenController.php
public function redirect()
{
$state = Str::random(32);
session(['raven.state' => $state]);
$params = [
'ver' => 3,
'url' => route('raven.callback'), // must be https
'date' => now()->utc()->toIso8601String(),
'desc' => config('app.name'),
'params' => $state,
];
// sign/assemble per the bundle's client wrapper
return redirect($this->raven->authenticateUrl($params));
}
public function callback(Request $request)
{
$response = $this->raven->validate($request->query('WLS-Response'), [
'expected_url' => route('raven.callback'),
'expected_params' => session()->pull('raven.state'),
'issue_tolerance' => 120, // seconds
]);
$user = User::firstOrCreate(
['crsid' => $response->principal()],
['name' => $response->displayName()]
);
Auth::login($user);
return redirect()->intended('/dashboard');
}
Mapping CRSID → user. Do this in one place (a service, not the controller). Auto-provision on first login is the standard pattern. Keep authorization in your own database, keyed by CRSID — do not rely on ptags alone for roles, because ptags changes are not reflected in existing sessions. If you do consume ptags, re-evaluate them on each login and consider a version column on the user to force role refresh.
Testing. You cannot rely on the live IdP in CI. Build a fixture set from a captured real response and assert all the negative cases:
it('rejects a tampered signature', fn () => /* flip a byte in sig */);
it('rejects an expired issue timestamp', fn () => /* age the fixture */);
it('rejects a replayed params', fn () => /* use the fixture twice */);
it('rejects an unknown kid', fn () => /* swap the kid */);
Run these in CI. This is the whole point of using the bundle as a reference — you get the protocol logic and the test cases you must replicate.
Integration tips.
onelogin/php-saml, Lightsaml) over the bundle's own crypto if it is available for your protocol generation. Only port the bundle if its validation is genuinely non-trivial relative to those libraries.The description in this repo is misleading. description.md describes Sentry error tracking via the Raven PHP client. The bundle is Raven authentication. If your real need is Sentry, use sentry/sentry-symfony (or sentry/sentry-laravel). If your need is Cambridge login, keep reading. Confirm which one the request actually means before starting.
WLS-Response validation failures are silent and confusing. The common causes, in order of frequency:
issue. Enforce NTP on all app servers and a tolerance window (e.g. 120s). A skewed server produces "expired response" errors that look like IdP outages.url mismatch. The callback URL you send and the one returned must match exactly — scheme, host, trailing slash included. Proxies and load balancers that terminate TLS can make the app see http where Raven saw https. Configure trusted proxies (TrustProxies in Laravel) or you will chase this for hours.kid rotation. Raven rotates keys. If you cache keys, provide a refresh path and alert on validation failure spikes. A stale key is a total login outage.params replay protection is in-process memory, multi-instance deploys will intermittently reject valid logins. Use Redis or the DB.Session key (skey) must be secret and stable per session. If you regenerate it between the redirect and the callback (e.g. session()->regenerate() before validation), the response will not verify. Validate first, then regenerate and log in.
params is your CSRF/state, but it is not enough on its own. Treat it as a nonce: generate server-side, store in session, compare with hash_equals, and delete after single use. Do not accept a params value the client supplied.
Case and normalization on principal. CRSIDs are typically lowercase; do not assume. Normalize on ingest (strtolower(trim())) or you will get duplicate user rows when one login path returns ABC123 and another abc123. Match CRSIDs case-insensitively in lookups.
ptags are authorization hints, not truth. They reflect group membership at the moment of the response. They go stale on the next click. If you need roles, either re-query at login or maintain them locally and reconcile.
Logout is not SSO logout. Authenticating via Raven does not propagate central logout. After a user logs out at Cambridge, they may still hold a valid local session. If your institution needs single logout, plan for it explicitly — the bundle does nothing here.
Do not install this as a dependency. 1 star, 0 dependents, single maintainer, Symfony 2 (EOL since 2018), no CI on modern PHP, no security review. In auth code, an unpatched validation library is a standing vulnerability. Copy the ~150 lines, own them, and test them against fixtures. If you need a maintained protocol layer, use a supported SAML/WebAuth library behind your own guard.
Extension points worth keeping when you port. (a) The signature/key-resolution step should be injectable so you can swap in a maintained verifier later. (b) The replay/state store should be an interface (session for single instance, Redis for multi). (c) The user-mapping step should be a service so tests can fake it. These three seams are where all the real failures and future changes will land.
How can I help you explore Laravel packages today?