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 Rajesh Kumar, a DevOps, SRE, DevSecOps, Cloud, and Platform Engineering expert passionate about sharing practical knowledge, real-world experiences, and industry best practices. I have worked at Cotocus and regularly write about technology, travel, investing, health, product reviews, and digital marketing through my various platforms.
I publish technical articles at DevOps School, travel stories at Holiday Landmark, stock market insights at Stocks Mantra, health and fitness guidance at My Medic Plus, product reviews at TrueReviewNow, and SEO and digital marketing strategies at Wizbrand.
Find Trusted Cardiac Hospitals
Compare heart hospitals by city and services โ all in one place.
Explore Hospitals