Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Janitor Laravel Package

signifly/laravel-janitor

Laravel Janitor is a lightweight Laravel package for automatically cleaning up stale Eloquent records. Define which models and records should be pruned, then let scheduled cleanup keep your database tidy without custom commands.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install the package

    composer require signifly/laravel-janitor
    php artisan vendor:publish --provider="Signifly\Janitor\JanitorServiceProvider"
    

    Publishing gives you config/janitor.php — this is the first file to read. It holds the proxy endpoint (/oauth/token), the OAuth client_id / client_secret (or the config keys that resolve them), the grant type, the cookie name and its lifetime/domain/SameSite settings, and the route prefix/middleware used for the package's routes.

  2. Make sure an OAuth server exists. The package is a proxy: it does not issue tokens itself. You either run Laravel Passport in the same app (php artisan passport:install, password grant client created) or point the config at a remote token endpoint. Without a password-grant client you will get a 400 invalid_client on the very first request — that is the most common "it doesn't work" report.

  3. Check the routes. Run php artisan route:list | grep -i auth (or janitor). You should see the login, refresh and logout endpoints registered by the package under whatever prefix/middleware you configured. If they are missing, the service provider isn't registered or the config has the routes disabled.

  4. First use case — SPA login without shipping a client secret. Your frontend posts { email, password } to the package's login route. The package calls the OAuth token endpoint server-side using the confidential client credentials, returns the short-lived access_token in the JSON body, and sets the long-lived refresh_token as an HttpOnly cookie. The browser never sees the client secret and JavaScript never touches the refresh token.

    const { access_token } = await fetch('/api/auth/login', {
      method: 'POST',
      credentials: 'include',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email, password }),
    }).then(r => r.json());
    
  5. Before you invest: the repository is archived (last release 2023-08-03). Read the source once — it is small, essentially one controller plus config — and be prepared to fork it into app/ or packages/, or to replace it with Passport's own first-party cookie guard / Sanctum if your Laravel version supports them.

Implementation Patterns

Token lifecycle in an HTTP client. The canonical loop is: request → 401 → POST to the refresh route (with credentials: 'include') → retry original request with the new access token. Guard against concurrent refreshes with a single in-flight promise, otherwise a burst of parallel 401s triggers several refresh calls and, if you ever enable refresh-token rotation, invalidates the token chain.

Guard your API routes with auth:api. Janitor issues tokens; it does not protect routes. Keep auth:api (or your configured guard) on the resource routes and treat the package purely as the credential-exchange surface. Set the guard's driver to passport in config/auth.php so $request->user() resolves from the bearer token.

Separate the login surface from the rest. Put the janitor routes in their own middleware group with throttle (e.g. throttle:5,1 on login) and no session/CSRF middleware, since they are stateless JSON endpoints. Registering them inside web is a classic misconfiguration that produces 419s.

Multi-client / multi-tenant setups. If you have several frontends or tenants, resolve the OAuth client dynamically rather than hardcoding one. The clean approach is a small subclass or a container binding that returns client credentials based on a request header or host, then rebind the package's service in a service provider. Keep per-client refresh cookies on distinct names or paths so they don't collide.

Customising responses and errors. Point the config at your own controller (or extend the package's) when you need to add user data, flags like must_change_password, or a consistent { message, errors } error envelope. Normalising the OAuth error payloads (invalid_grant vs invalid_credentials) here saves every consumer from mapping them.

Logout properly. Remember to revoke tokens, not just clear the cookie — delete the user's access tokens ($user->tokens()->delete()) and/or revoke the refresh token so a stolen cookie cannot be replayed. The package clears the cookie; server-side revocation is your job.

Testing. Feature tests should not round-trip through a real OAuth server. Use Passport::actingAs($user) for authenticated endpoints and only cover the proxy endpoints themselves with a mocked HTTP client (Http::fake() on the token URL) asserting the request payload, the JSON shape, and the Set-Cookie headers.

Gotchas and Tips

  • Archived package. Pin the version in composer.json and check its Laravel illuminate/* constraint against your framework version. On newer Laravel releases the auto-discovery and middleware signatures may drift; a local fork is the safest route if you plan to keep it long-term.
  • Cookie and SPA-domain mismatch. If the SPA is on a different origin than the API, the refresh cookie needs SameSite=None; Secure and a Domain covering both hosts. Locally, localhost and 127.0.0.1 are different sites for cookie purposes — testing on http://localhost:3000 against http://localhost:8000 with SameSite=Lax silently drops the cookie on cross-site refreshes.
  • HTTPS only. Secure cookies are required for cross-site use, so a plain-HTTP staging environment will appear to "lose" the refresh token.
  • Refresh token rotation is not on by default in Passport (and is not enabled by janitor). Without rotation, a leaked refresh cookie is valid until expiry. If you need rotation, you must enable it on the Passport client and handle the "old token is now invalid" race described above.
  • Expired tokens look like auth failures. A 401 from a protected route is indistinguishable from a revoked token. Distinguish token_expired from invalid_token in the frontend interceptor, or you'll loop refreshes for revoked users.
  • Clock skew and TTLs. Passport's default access-token TTL (1 year historically) is far longer than the "short-lived access token" assumption the proxy pattern is built on. Set Passport::tokensExpireIn() and refreshTokensExpireIn() explicitly, and keep the cookie's max-age in sync with the refresh TTL or the cookie outlives the token.
  • Missing cleanup. Expired refresh tokens accumulate in oauth_refresh_tokens. Schedule passport:purge (or the package's equivalent) in Kernel::schedule.
  • Rate limiting is not built in. Brute-force protection on the login proxy is entirely your responsibility — an unthrottled proxy is a password-spraying oracle.
  • Keys and encryption. passport:keys must run in deployment; without it token issuance throws an obscure encryption error that surfaces as a 500 from the login route. If you proxy to a remote auth server, that key never lives in this app at all.
  • Debugging tip. Log the outbound token request (URL, grant type, client id — never the secret) at debug level. Almost every failure is a wrong grant type, wrong client, or a password-grant client that was created with --personal instead of --password.
  • Extension points to look at first: the published config's controller/route bindings and the service provider's container bindings. Those are the two places to swap behaviour without editing vendor files; anything deeper is better served by copying the package into your own namespace.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core