using Laravel Passport—the oauth_access_tokens
table balloons when tokens aren’t expiring, not being revoked on logout, or not being purged. Here’s a practical, safe cleanup + prevention plan you can apply right now.
1) Inspect what’s growing
-- Table sizes (MB)
SELECT TABLE_NAME, ROUND((DATA_LENGTH+INDEX_LENGTH)/1024/1024,2) AS MB
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME IN ('oauth_access_tokens','oauth_refresh_tokens')
ORDER BY MB DESC;
-- How many active (not revoked) tokens per user
SELECT user_id, COUNT(*) AS tokens
FROM oauth_access_tokens
WHERE revoked = 0
GROUP BY user_id
ORDER BY tokens DESC
LIMIT 20;
-- Expired tokens still present?
SELECT COUNT(*)
FROM oauth_access_tokens
WHERE expires_at IS NOT NULL AND expires_at < NOW();
Code language: PHP (php)
2) One-time purge (safe cleanup)
Backup first. Then, if you’re on Passport 10+:
php artisan passport:purge --revoked --expired
Code language: CSS (css)
This removes revoked and expired access/refresh tokens.
If you don’t have the purge command, do it manually:
-- Revoke + delete obviously old tokens (example: older than 90 days)
UPDATE oauth_access_tokens SET revoked = 1
WHERE (expires_at IS NOT NULL AND expires_at < NOW()) OR created_at < NOW() - INTERVAL 90 DAY;
-- Clean up refresh tokens linked to revoked access tokens
DELETE rt FROM oauth_refresh_tokens rt
JOIN oauth_access_tokens at ON rt.access_token_id = at.id
WHERE at.revoked = 1;
-- Optionally delete the revoked access tokens themselves
DELETE FROM oauth_access_tokens WHERE revoked = 1;
Code language: JavaScript (javascript)
Then reclaim disk space (if you use file-per-table InnoDB):
OPTIMIZE TABLE oauth_access_tokens;
OPTIMIZE TABLE oauth_refresh_tokens;
3) Fix the root causes
A) Set short, sane expirations
In App\Providers\AuthServiceProvider.php
:
use Laravel\Passport\Passport;
public function boot()
{
$this->registerPolicies();
Passport::tokensExpireIn(now()->addHours(2)); // access tokens
Passport::refreshTokensExpireIn(now()->addDays(14)); // refresh tokens
Passport::personalAccessTokensExpireIn(now()->addMonths(3));
}
Code language: PHP (php)
Deploy, then php artisan config:clear && php artisan cache:clear
.
B) Revoke on logout (don’t leave tokens hanging)
If you’re issuing access tokens on login, make sure logout revokes them:
public function logout(Request $request)
{
$token = $request->user()->token();
if ($token) {
$token->revoke();
// Also revoke its refresh tokens
\DB::table('oauth_refresh_tokens')
->where('access_token_id', $token->id)
->update(['revoked' => true]);
}
return response()->json(['message' => 'Logged out']);
}
Code language: PHP (php)
If you want to nuke all a user’s tokens on logout:
$request->user()->tokens()->delete();
\DB::table('oauth_refresh_tokens')->whereIn(
'access_token_id',
\DB::table('oauth_access_tokens')->where('user_id', $request->user()->id)->pluck('id')
)->delete();
Code language: PHP (php)
C) Don’t issue a new token on every request
Audit your login/auth flow—ensure you only create a token at login (or first app start) and then reuse it. If you’re creating tokens during each API call, that’s the main culprit.
D) Schedule automatic purging
In app/Console/Kernel.php
:
protected function schedule(Schedule $schedule)
{
// Passport 10+:
$schedule->command('passport:purge --revoked --expired')->dailyAt('02:30');
// Optional: extra safety—purge very old tokens regardless
// $schedule->call(fn () => \DB::table('oauth_access_tokens')
// ->where('created_at','<',now()->subMonths(6))->delete()
// )->weeklyOn(1, '03:00');
}
Code language: PHP (php)
Ensure your server cron runs Laravel’s scheduler:
* * * * * cd /path/to/app && php artisan schedule:run >> /dev/null 2>&1
Code language: JavaScript (javascript)
4) If you’re not tied to Passport
For SPAs and first-party mobile apps, consider Laravel Sanctum. It’s lighter (personal_access_tokens
), easier to manage, and usually grows less aggressively.
5) Quick “emergency” reduction
If you must free space fast and accept losing old sessions:
-- Delete tokens older than 30 days (adjust to your risk appetite)
DELETE rt FROM oauth_refresh_tokens rt
JOIN oauth_access_tokens at ON rt.access_token_id = at.id
WHERE at.created_at < NOW() - INTERVAL 30 DAY;
DELETE FROM oauth_access_tokens
WHERE created_at < NOW() - INTERVAL 30 DAY;
Then OPTIMIZE TABLE
as above.
I’m a DevOps/SRE/DevSecOps/Cloud Expert passionate about sharing knowledge and experiences. I have worked at Cotocus. I share tech blog at DevOps School, travel stories at Holiday Landmark, stock market tips at Stocks Mantra, health and fitness guidance at My Medic Plus, product reviews at TrueReviewNow , and SEO strategies at Wizbrand.
Do you want to learn Quantum Computing?
Please find my social handles as below;
Rajesh Kumar Personal Website
Rajesh Kumar at YOUTUBE
Rajesh Kumar at INSTAGRAM
Rajesh Kumar at X
Rajesh Kumar at FACEBOOK
Rajesh Kumar at LINKEDIN
Rajesh Kumar at WIZBRAND