renoki-co/hej
Hej is a Laravel package for adding user invitations to your app. It provides invite codes, acceptance flows, expiration, and Eloquent integration so you can build invite-only registration quickly and cleanly.
Hej is a Socialite wrapper: it ships the OAuth redirect/callback routes, a controller that maps provider payloads onto your user + a social-account record, and the config that drives it. 4.0.0 is a breaking release — plan a config re-publish before you touch anything else.
composer require renoki-co/hej:^4.0 will fail on 8.x and that's intentional, not a resolution bug.composer require renoki-co/hej
php artisan vendor:publish --tag="hej-config"
php artisan vendor:publish --tag="hej-migrations"
php artisan migrate
config/hej.php forward, the package will boot but every login attempt will bounce — an unlisted provider is treated as unknown. Diff your config against the published stub rather than editing over the top.config/services.php (github, google, gitlab, …) plus the usual *_CLIENT_ID / *_CLIENT_SECRET / *_REDIRECT_URI env vars. The redirect URI must exactly match what's registered with the provider.github to the allowed-providers array in hej.php, set the success redirect to /dashboard, then hit /auth/github/redirect. You should land back on the callback, get a user, and see a social-account row.Where to look first, in order: config/hej.php (the whole 4.0 migration lives here), the package's route registration (to learn the route names so you can route('hej.redirect', $provider) instead of hardcoding URLs), the callback controller (your extension point now), and the social-account model (which lost $fillable in this release).
Subclass the callback controller — that is the sanctioned extension point. With $fillable removed from the model, mass assignment no longer carries your custom columns, so the "add a field" workflow moved into the controller. Extend the package controller, override the method that persists the account, set your attributes explicitly, and point the package route at your class (via the config's controller/route binding or by declaring your own routes).
class HejController extends \RenokiCo\Hej\Http\Controllers\HejController
{
protected function updateSocialAccount($account, $providerUser)
{
$account->forceFill([
'nickname' => $providerUser->getNickname(),
'avatar' => $providerUser->getAvatar(),
]);
return parent::updateSocialAccount($account, $providerUser);
}
}
Drive redirects from config, not the controller. The new redirects section exists so that SPA/Inertia/Livewire flows can point at a front-end route without forking the controller. Keep environment-specific destinations (local /dashboard, prod /onboarding) in config/hej.php and out of code. If the config supports per-provider overrides, prefer those over a single global value.
Treat providers as an allow-list, not a discovery mechanism. The allowed array is the security boundary — it prevents /auth/{anything}/redirect from spinning up arbitrary Socialite drivers. Register only what you actually ship, and gate additions behind a config review rather than editing in production.
Multi-provider linking. Model the social account as (provider, provider_id) → user_id and let one user hold several rows. In the callback, look up by provider + provider_id first; fall back to email-match for account linking; only then create a new user. This is where most real bugs land — decide up front whether a second provider with a matching email auto-links or prompts.
Custom routes for prefixes and throttling. Disable the package routes and declare your own on a group with prefix('auth'), middleware(['web', 'throttle:10,1']), pointing at your controller subclass. Good for localization (/{locale}/auth/...) and for keeping the callback out of broad middleware.
Stateless / API mode. For SPA or mobile, don't use the session-based redirect route at all — call Socialite statelessly from your own endpoint and have your controller subclass mint a Sanctum token in the callback instead of logging in. The config-driven redirects are then largely bypassed, which is fine; return JSON.
Testing. Fake Socialite rather than the network:
Socialite::shouldReceive('driver->user')->andReturn($fakeProviderUser);
Assert three things per provider: the redirect route 302s to the provider, the callback creates exactly one social-account row, and a repeat callback updates rather than duplicates.
/. Republish, diff, merge — don't hand-patch.$fillable removal fails quietly in the worst way. SocialAccount::create($providerUser->toArray()) no longer throws a visible mass-assignment error in every path; if the attributes aren't fillable they're simply dropped, and you get a row with nulls. Grep your app for ->create( and ->update( against the package model and switch them to explicit assignment or forceFill.php artisan config:clear && php artisan route:clear. A cached config from 3.x surviving a composer update is the single most common "4.0 is broken" report.InvalidStateException on callback is almost always a redirect-URI mismatch (scheme, www, port, trailing slash) or a session/cookie-domain problem, not a package bug. Confirm the URI is byte-identical to the provider dashboard entry.config/services.php key must all agree. google vs google_oauth style drift produces confusing "driver not supported" errors.?redirect= parameter. Don't — keep destinations server-side unless you validate against an allow-list of paths.(provider, provider_id). Without it, concurrent or retried callbacks create duplicate account rows; the controller's "find or create" can't save you from a race.$fillable audit in one branch before enabling the package in production; the three changes interact and a partial upgrade looks like a routing bug rather than a mass-assignment one.How can I help you explore Laravel packages today?