saucebase/auth
Authentication package for Laravel and the Saucebase stack, providing a simple, flexible way to add login, registration, password resets, and session or token-based auth to your applications. Integrates cleanly with Laravel's guards, user providers, and middleware.
Treat this as a module inside a Saucebase application, not a standalone Composer package you drop into any Laravel app. It assumes the Saucebase core is present: Inertia (Vue or React) pages, Filament admin panels, the core settings modal, Home::url() redirects, and @saucebase/vue (or @saucebase/react) navigation helpers.
Minimal steps:
resources/boost/skills/saucebase-auth-development/SKILL.md (and agents/skills/saucebase-auth-development/SKILL.md) is the de-facto documentation. The paths in it are relative to modules/auth/. There is no published user manual beyond this.src/Settings and installed from database/settings — if AuthSettings values are missing from your DB, the middleware will treat features as off.manage users or impersonate users by the seeders:
php artisan auth:make-admin [email protected]
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET / GOOGLE_CLIENT_REDIRECT_URI
GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRET / GITHUB_CLIENT_REDIRECT_URI
Redirect URIs default to /auth/socialite/callback/{provider} style paths — verify against what your provider console holds./auth/login, /auth/register, then /auth/magic-link after enabling it in the Filament AuthenticationSettings page. From there, the day-to-day work is almost always consuming routes and events rather than editing this module.Where to look when something breaks, in order: the SKILL.md, routes/*.php in the module, AuthSettings, then the controllers named in the skill's key-files table.
Redirect contract — hook your onboarding here. Every entry point (password login, magic link, social sign-in, verification) ends at redirect()->intended(Home::url($request, $reason)) with $reason one of login, registered, verified. If you need post-registration onboarding, branch on the registered reason in your home resolver instead of adding a listener that redirects.
Event-driven side effects. Subscribe rather than edit controllers:
Registered → AssignUserRole, SendWelcomeNotificationLogin → UpdateUserLastLoginReturningUserAuthenticated → SendLoginNotification (dispatched by password, magic-link and social sign-in, but not registration)EnterImpersonation → session historyAdding your own listener to ReturningUserAuthenticated gives you "new device" or audit hooks across all three sign-in paths for free.
Social accounts on the User model. User uses the Sociable trait, giving socialAccounts, connected_providers and disconnect. For a custom "connected accounts" screen, read connected_providers and call the same SocialiteService methods the controller does rather than reimplementing find-or-create; the service already handles linking, the account-takeover guard, and the "only login method" guard.
Respect the dual-flow OAuth branch. SocialiteController::callback() checks Auth::check(): guest → find/create + login + intended; authenticated → link and redirect to settings.profile. If you build a custom link button, it must be a guest-free context or you will silently link instead of log in.
Extend the settings modal from your own module. Do not edit ProfileSection / SecuritySection; contribute a new section from src/Settings with an order value. Profile is 10, Security is 15 — pick something above 15 to sit after credentials.
Use the canonical route names. settings.profile, settings.profile.password.update, password.update, logout, auth.socialite.disconnect. Core resolves some of these by name (PasswordChangedNotification links to route('settings.profile')), so renaming breaks other packages.
Modal-aware links. If you add a "Sign In" entry point anywhere, use the ModalLink behaviour (which sends Modal::HEADER_MODAL) only when you actually want the modal; every other Link correctly stays a full page. Consume the shared auth.modal_enabled prop rather than querying AuthSettings in your own controller.
Admin surfaces. UserResource is a complete list/create/view/edit resource. When adding fields via UserForm, remember role assignment is gated: a creator without role-assignment rights yields a user (CreateUser::afterCreate()), and the Role field is not saved when hidden or disabled.
Impersonation as a feature, not a hack. Use filament-impersonate's leave route to stop, and let ReimpersonateController handle "back to previous user". The history is capped at 4 IDs in session and 3 in the UI, filtered for deleted users and self.
User does not implement MustVerifyEmail (the import is commented out), so the verified middleware is a pass-through. email_verified_at is still kept accurate — do not build a "verified only" gate on top of the middleware and assume it works.email_verified_at is deliberately not fillable. No request payload can self-verify. SocialiteService sets it with forceFill(). If you write a bulk-import or admin-edit path, replicate that explicitly.ProfileController::updateInfo() clears the timestamp when the address actually changed). Expect a re-verification email; don't cache "verified" in your own table.AuthenticateSession in the web group, an app-wide change still pending a regression pass (sc-714). If you're auditing session security, this is the known gap.UPDATE on unused+unexpired rows, so concurrent clicks resolve to exactly one login.404s from /auth/* are usually a setting, not a routing bug. EnsureRegistrationEnabled, EnsureMagicLinkEnabled, EnsureSocialiteProviderEnabled each 404 their routes while the corresponding AuthSettings flag is off. Check the Filament AuthenticationSettings page before debugging route registration.modal_enabled and Modal::HEADER_MODAL are required; otherwise Inertia::render(). This matters because Modal::toResponse() falls back to the referer, so a plain in-app link to /auth/login would otherwise render a modal over the previous page. The base-URL header also counts as "asking" — it's what makes validation-failed modals come back as modals instead of full pages.email, intentionally: a flashed error would land on the page behind the modal where nothing renders it. Don't "improve" this into a flash message.AuthLink swaps siblings only after the close has navigated back. Opening the next modal too early records the closing modal's URL as its base and gets torn down. A second click mid-swap is ignored by design.email|ip. It fires Lockout and throws AuthException::throttle($seconds), cleared on success. This is PHP-tested only — Playwright workers share one IP, so an e2e lockout leaks into unrelated login tests. Keep lockout assertions in PHPUnit.socialAccounts->count() === 1 && !$user->password throws cannotDisconnectOnlyMethod). Linking a provider ID owned by another user throws accountAlreadyLinked — that's the account-takeover defence, not a bug report.manage users opens the Users page; impersonate users is required to impersonate, and both must be granted explicitly. UserPolicy refuses update/delete/impersonate on admins, but an admin passes everything via Gate::before — so those denials bind staff, not admins. Nobody can delete their own account from the resource.settings.* prefix even though profile/security live in this module and settings.profile merely redirects into the #settings/profile fragment. Renaming to auth.profile.* would break core and PasswordChangedNotification.PasswordController::update() serves two routes (password.update and settings.profile.password.update) and always redirects to settings.profile. If you add a third caller expecting a different redirect, you'll need to branch.resources/js/app entry is load-bearing — it registers the logout/settings/profile icons and the logout action handler via registerIcon()/registerAction(). Drop or reorder that entry and the navbar logout silently disappears.How can I help you explore Laravel packages today?