waterhole/sso
waterhole/sso adds single sign-on support to Waterhole, letting forum users authenticate through an external identity provider. It streamlines login, centralizes user identities, and reduces credential management for Laravel-based communities.
The package is intentionally small: it wraps the logic for producing the signed payload/URL that Waterhole (the Laravel forum package) expects when accepting a user from an external identity source. Think of it as the "client side" of Waterhole's SSO handshake — you authenticate users in your app, then hand them to Waterhole already logged in.
Minimal setup:
composer require waterhole/ssophp artisan vendor:publish --tag=waterhole-sso-config) and set the shared secret. This secret must be generated/pasted into the Waterhole admin panel under SSO settings — the two sides must match byte-for-byte..env (e.g. WATERHOLE_SSO_SECRET=...) and reference it from config/waterhole-sso.php. Never commit the raw value.Where to look first:
README.md for the canonical usage snippet — it is short, read it fully before improvising.config/waterhole-sso.php for the secret, base URL of the forum, and any TTL/algorithm knobs.src/ — the signing helper class is usually the only meaningful file; skim it to confirm exactly what claims are emitted and how the signature is built.First use case: a "Community" / "Forum" button in your app's nav or user menu. Clicking it hits your controller, which calls the package helper with the current Auth::user(), and issues a redirect()->away(...) to the generated Waterhole URL. The user arrives on the forum already authenticated, with name/email pre-populated. That single flow is 90% of what this package is for.
Dedicated controller action (recommended). Keep SSO in its own invokable controller rather than a closure, so you can test it and add logging:
// routes/web.php
Route::get('/forum', ForumRedirectController::class)
->middleware('auth')
->name('forum.redirect');
The controller resolves the current user, asks the package for the signed redirect target, and returns redirect()->away($url). Do not return redirect($url) — this is an external domain.
Gate access with policy, not just auth. If forum access is role- or subscription-gated, check that before generating the URL. Waterhole will happily accept a signed payload for a user you didn't intend to let in; your app is the only place that decision can be made.
Sync on login, don't sync on every request. The SSO handshake is a one-shot redirect, not a session bridge. Treat Waterhole sessions as independent: users log into Waterhole once via your redirect, and stay logged in there until that session expires. If you need "logging out of the app logs out of the forum", do it as an explicit best-effort step (call Waterhole's logout endpoint or redirect through it) and accept it can fail silently.
Multi-forum / multi-tenant. If you run more than one Waterhole instance, avoid a single global secret. Bind the config per tenant (e.g. resolve secret from a Tenant model) and pass it into the helper explicitly rather than relying on the singleton config. Cache the resolved config per tenant key.
Prefilling and roles. Use the package's claim/callback extension (most SSO helpers expose a way to add claims or override the payload) to send display name, email, avatar URL, and group/role hints. Keep the payload minimal — anything you send becomes trusted data in the forum. Do not send passwords, tokens, or internal IDs you don't want visible.
UI integration. In Blade/Inertia, link the button to the named route so the URL is generated for you. For SPA frontends, return the signed URL from a small JSON endpoint (GET /api/forum/link) behind auth:sanctum and let the client window.location.assign() it. Signed URLs are short-lived, so fetch on click, not on page load.
Testing. Feature-test the controller: assert a redirect to the configured forum host, parse the query string, and re-compute the signature with the same secret to assert it matches. Also test the negative cases — guest gets redirected to login, unsubscribed user gets a 403, and a missing secret throws rather than silently producing a broken URL.
Local development. Run Waterhole locally alongside your app and point the configured base URL at it (e.g. http://waterhole.test). Use a distinct dev secret from production and keep both in .env / your secret manager respectively.
.env, or a base64 vs raw string difference produces a signature Waterhole rejects with a generic error. Copy the secret once, trim it, and compare lengths before debugging anything else.?ref=, a fragment) afterward, the signature breaks. Generate the URL, then treat it as immutable.+, @, spaces, and ampersands. Let the package encode; never string-concatenate query parameters manually. If you must construct a URL yourself, use http_build_query with PHP_QUERY_RFC3986.Referrer-Policy and avoid putting the SSO URL in page markup before the click.php artisan config:clear locally and remember config:cache in production means .env changes need a re-cache (or a deploy) to take effect. A stale cached secret looks exactly like a wrong secret.vendor/ — and consider whether Waterhole's side supports it at all.How can I help you explore Laravel packages today?