jundayw/tokenable
jundayw/tokenable is a Laravel package that adds token support to Eloquent models, helping you generate and manage tokens for authentication or access control. A lightweight helper for token-based APIs and PHP applications.
composer require jundayw/tokenable
php artisan vendor:publish --provider="Jundayw\Tokenable\TokenableServiceProvider"
php artisan migrate
Start by reading these, in order:
vendor/jundayw/tokenable/README.md — the only usage documentation.config/tokenable.php (or whatever the published config is named) — token table, TTLs, guard names.src/ — especially traits, the token model, middleware, and the service provider. With a 2-star package, the source is the documentation.database/migrations/ — confirm the table name and columns before migrating into an existing schema.Add the package trait to every authenticatable model you want to issue tokens for:
use Jundayw\Tokenable\Traits\HasTokenable; // confirm exact namespace in src/
class User extends Authenticatable
{
use HasTokenable;
}
The first end-to-end use case is a single API login that returns an access token and a refresh token, plus one protected route:
Route::post('/login', function (Request $request) {
$user = User::where('email', $request->email)->firstOrFail();
// Method names are indicative — verify against the trait.
$token = $user->createToken('api');
return response()->json([
'access_token' => $token->plainTextToken,
'refresh_token' => $token->refreshToken ?? null,
]);
});
Route::middleware('auth:api')->get('/me', fn (Request $request) => $request->user());
Register the package's guard/middleware in config/auth.php and bootstrap/app.php (Laravel 11+) or Kernel.php before testing. If the README does not show the guard driver name, search the source for Auth::extend / Auth::viaRequest.
Login controller pattern. Keep token issuance in a small action or service, not scattered across controllers. Validate credentials first, then issue tokens. Return the plain-text token exactly once; only the hash should be persisted.
final class IssueToken
{
public function __invoke(User $user, string $platform = 'api'): array
{
$access = $user->createToken($platform);
$refresh = $user->createRefreshToken($platform); // indicative
return [
'token_type' => 'Bearer',
'access_token' => $access->plainTextToken,
'expires_in' => config('tokenable.access_ttl'),
'refresh_token'=> $refresh->plainTextToken,
];
}
}
Route protection. Apply the package middleware to API route groups rather than individual routes, and keep a separate group for unauthenticated auth endpoints:
Route::post('/login', [AuthController::class, 'login']);
Route::post('/refresh', [AuthController::class, 'refresh']);
Route::middleware(['auth:api'])->group(function () {
Route::get('/me', [AuthController::class, 'me']);
});
Multi-model support. The value of this package is that tokens are polymorphic. Use it when you have User, Admin, and Partner models that all need API credentials. Give each model its own guard in config/auth.php with the correct provider, otherwise auth:api will resolve the wrong model:
'guards' => [
'api' => ['driver' => 'tokenable', 'provider' => 'users'],
'admin' => ['driver' => 'tokenable', 'provider' => 'admins'],
],
Multi-platform tokens. Issue one token per platform/device and store the platform label. This lets you revoke “all web sessions” without touching mobile sessions:
$user->createToken('web');
$user->createToken('ios');
$user->createToken('ssr');
// Later:
$user->tokens()->where('platform', 'web')->delete();
Confirm whether the package stores platform on the token row or expects you to use token names. If it does not store device metadata, add a nullable platform column via your own migration.
Refresh-token rotation. Access tokens should be short-lived (5–15 minutes); refresh tokens long-lived. On refresh, revoke the presented refresh token and issue a new pair. This limits replay damage if a refresh token leaks:
public function refresh(Request $request)
{
$user = $request->user(); // resolved from refresh token
$request->token->delete(); // revoke used refresh token
return $this->issue($user, $request->token->platform);
}
If the package does not support rotation natively, wrap it in your own endpoint and treat the refresh token as a one-time credential.
SPA vs API vs SSR. For SPAs, prefer an HttpOnly cookie carrying the refresh token and a short-lived bearer access token in memory. For SSR, set the access token in an HttpOnly cookie and let the package guard read it, avoiding token exposure in HTML. For pure APIs, use Authorization: Bearer. Do not mix cookies and bearer tokens on the same route group without CSRF protection.
Cleanup and observability. Schedule pruning of expired and revoked tokens:
Schedule::command('tokenable:prune')->daily(); // confirm command name
Log token issuance and revocation events with user ID and platform. Since refresh tokens are long-lived credentials, an audit trail is useful when debugging “why was this session logged out.”
Testing. Custom guards often bypass Laravel's actingAs(). For feature tests, obtain a real token via the login endpoint or use the package's token factory if one exists. Add a test that asserts a revoked token returns 401 and an expired token returns 401.
composer.json, read the token hashing code yourself, and be prepared to fork. Do not adopt it for a long-lived product without a maintenance plan.createToken, createRefreshToken, revoke, etc.) in src/ before writing controllers. The code examples above are indicative, not guaranteed.tokens table will conflict. Change the table name in the published config, or remove the competing package.php artisan migrate before publishing migrations creates an empty or missing schema. Publish config and migrations, inspect them, then migrate.php artisan config:clear after editing config/auth.php.token column, treat the package as insecure for production and hash before persisting.auth:api can authenticate an Admin against a User route if providers and guards are misconfigured. Add a guard-per-model and test cross-model access explicitly.bootstrap/app.php (Laravel 11+) rather than assuming it is auto-discovered.php artisan config:cache freezes the published config. Any guard or TTL change requires a cache clear during deployment.TokenService interface so you can replace it later with Sanctum or a hand-rolled implementation.How can I help you explore Laravel packages today?