10 KiB
Testing Refresh Token System
This guide provides step-by-step instructions to test the refresh token implementation.
Prerequisites
-
Database is running and migrations are applied:
npm run migration:run -
Application is running:
npm run start:dev -
Seed data is loaded (includes admin user):
npm run seed
Test Scenarios
1. Test User Login (Get Tokens)
Request:
curl -X POST http://localhost:3000/api/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "admin@retailpos.com",
"password": "Admin123!"
}'
Expected Response:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6...",
"user": {
"id": "uuid",
"email": "admin@retailpos.com",
"name": "Admin User",
"roles": ["admin"],
"isActive": true,
"createdAt": "2025-01-15T10:00:00.000Z"
}
}
Verification:
- ✅ Response includes
access_token - ✅ Response includes
refresh_token - ✅ Response includes user details
- ✅ HTTP status is 200
Save the tokens:
# Save for next tests
export ACCESS_TOKEN="<your-access-token>"
export REFRESH_TOKEN="<your-refresh-token>"
2. Test Access Token Works
Use the access token to access a protected endpoint:
Request:
curl -X GET http://localhost:3000/api/auth/profile \
-H "Authorization: Bearer $ACCESS_TOKEN"
Expected Response:
{
"success": true,
"data": {
"id": "uuid",
"email": "admin@retailpos.com",
"roles": ["admin"]
}
}
Verification:
- ✅ Protected endpoint returns user data
- ✅ HTTP status is 200
- ❌ Without token returns 401
3. Test Token Refresh
Exchange refresh token for new tokens:
Request:
curl -X POST http://localhost:3000/api/auth/refresh \
-H "Content-Type: application/json" \
-d "{
\"refreshToken\": \"$REFRESH_TOKEN\"
}"
Expected Response:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "x1y2z3a4b5c6d7e8f9g0h1i2j3k4l5m6n7o8p9q0r1s2t3u4v5w6...",
"user": {
"id": "uuid",
"email": "admin@retailpos.com",
"name": "Admin User",
"roles": ["admin"],
"isActive": true,
"createdAt": "2025-01-15T10:00:00.000Z"
}
}
Verification:
- ✅ New
access_tokenis different from old one - ✅ New
refresh_tokenis different from old one - ✅ HTTP status is 200
- ✅ User details are returned
Save new tokens:
export NEW_ACCESS_TOKEN="<new-access-token>"
export NEW_REFRESH_TOKEN="<new-refresh-token>"
4. Test Token Rotation (Old Token Should Be Revoked)
Try to use the OLD refresh token again:
Request:
curl -X POST http://localhost:3000/api/auth/refresh \
-H "Content-Type: application/json" \
-d "{
\"refreshToken\": \"$REFRESH_TOKEN\"
}"
Expected Response:
{
"success": false,
"error": {
"statusCode": 401,
"message": "Invalid refresh token"
}
}
Verification:
- ✅ HTTP status is 401
- ✅ Error message indicates invalid token
- ✅ Old token cannot be reused (token rotation works!)
5. Test Logout
Logout and revoke the refresh token:
Request:
curl -X POST http://localhost:3000/api/auth/logout \
-H "Content-Type: application/json" \
-d "{
\"refreshToken\": \"$NEW_REFRESH_TOKEN\"
}"
Expected Response:
{
"success": true,
"message": "Logged out successfully"
}
Verification:
- ✅ HTTP status is 200
- ✅ Success message returned
6. Test Revoked Token Cannot Be Used
Try to use the revoked token:
Request:
curl -X POST http://localhost:3000/api/auth/refresh \
-H "Content-Type: application/json" \
-d "{
\"refreshToken\": \"$NEW_REFRESH_TOKEN\"
}"
Expected Response:
{
"success": false,
"error": {
"statusCode": 401,
"message": "Invalid refresh token"
}
}
Verification:
- ✅ HTTP status is 401
- ✅ Revoked token cannot be used
7. Test Revoke All Tokens
Login again and test revoking all tokens:
Step 1 - Login:
curl -X POST http://localhost:3000/api/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "admin@retailpos.com",
"password": "Admin123!"
}'
Save the tokens:
export ACCESS_TOKEN="<access-token>"
export REFRESH_TOKEN="<refresh-token>"
Step 2 - Revoke All Tokens:
curl -X POST http://localhost:3000/api/auth/revoke-all \
-H "Authorization: Bearer $ACCESS_TOKEN"
Expected Response:
{
"success": true,
"message": "All refresh tokens revoked successfully"
}
Step 3 - Verify Token is Revoked:
curl -X POST http://localhost:3000/api/auth/refresh \
-H "Content-Type: application/json" \
-d "{
\"refreshToken\": \"$REFRESH_TOKEN\"
}"
Verification:
- ✅ Revoke all returns success
- ✅ All tokens are revoked
- ✅ Cannot use any refresh token after revoke-all
8. Test Invalid Scenarios
8.1 Invalid Refresh Token
curl -X POST http://localhost:3000/api/auth/refresh \
-H "Content-Type: application/json" \
-d '{
"refreshToken": "invalid-token-12345"
}'
Expected: 401 Unauthorized
8.2 Missing Refresh Token
curl -X POST http://localhost:3000/api/auth/refresh \
-H "Content-Type: application/json" \
-d '{}'
Expected: 400 Bad Request (validation error)
8.3 Expired Access Token
Wait for access token to expire (24 hours by default) or manually set a short expiry:
Expected: 401 Unauthorized when using expired access token
9. Database Verification
Connect to the database and verify tokens are stored correctly:
# Connect to PostgreSQL
psql -h localhost -U postgres -d retail_pos
# Or for remote database
psql -h pg-30ed1d6a-renolation.b.aivencloud.com -p 20912 -U avnadmin -d defaultdb
Query 1 - View Refresh Tokens:
SELECT
id,
LEFT(token, 20) || '...' as token_preview,
"userId",
"expiresAt",
"isRevoked",
"createdAt"
FROM refresh_tokens
ORDER BY "createdAt" DESC
LIMIT 10;
Query 2 - Count Active Tokens:
SELECT COUNT(*) as active_tokens
FROM refresh_tokens
WHERE "isRevoked" = false
AND "expiresAt" > NOW();
Query 3 - Tokens Per User:
SELECT
u.email,
COUNT(*) as token_count,
SUM(CASE WHEN rt."isRevoked" = false THEN 1 ELSE 0 END) as active_count
FROM refresh_tokens rt
JOIN users u ON u.id = rt."userId"
GROUP BY u.email;
Verification:
- ✅ Tokens are stored as hashed values
- ✅ Revoked tokens have
isRevoked = true - ✅ Tokens have proper expiration dates
- ✅ Foreign key relationship to users exists
10. Load Testing (Optional)
Test multiple concurrent refresh operations:
# Create a simple load test script
cat > test_refresh.sh << 'EOF'
#!/bin/bash
# Login first
RESPONSE=$(curl -s -X POST http://localhost:3000/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"admin@retailpos.com","password":"Admin123!"}')
REFRESH_TOKEN=$(echo $RESPONSE | jq -r '.refresh_token')
# Perform 10 sequential refreshes
for i in {1..10}; do
echo "Refresh attempt $i"
RESPONSE=$(curl -s -X POST http://localhost:3000/api/auth/refresh \
-H "Content-Type: application/json" \
-d "{\"refreshToken\":\"$REFRESH_TOKEN\"}")
REFRESH_TOKEN=$(echo $RESPONSE | jq -r '.refresh_token')
if [ "$REFRESH_TOKEN" == "null" ]; then
echo "Failed at attempt $i"
break
fi
done
EOF
chmod +x test_refresh.sh
./test_refresh.sh
Verification:
- ✅ All refreshes succeed
- ✅ Each refresh invalidates previous token
- ✅ No database errors
Postman/Insomnia Collection
For easier testing, import this collection:
Login
POST http://localhost:3000/api/auth/login
Content-Type: application/json
{
"email": "admin@retailpos.com",
"password": "Admin123!"
}
Refresh Token
POST http://localhost:3000/api/auth/refresh
Content-Type: application/json
{
"refreshToken": "{{refresh_token}}"
}
Get Profile
GET http://localhost:3000/api/auth/profile
Authorization: Bearer {{access_token}}
Logout
POST http://localhost:3000/api/auth/logout
Content-Type: application/json
{
"refreshToken": "{{refresh_token}}"
}
Revoke All Tokens
POST http://localhost:3000/api/auth/revoke-all
Authorization: Bearer {{access_token}}
Expected Test Results Summary
| Test Case | Expected Result | Status |
|---|---|---|
| Login | Returns access_token + refresh_token | ✅ |
| Access protected endpoint | Returns user data | ✅ |
| Refresh token | Returns new tokens | ✅ |
| Token rotation | Old token becomes invalid | ✅ |
| Logout | Token is revoked | ✅ |
| Use revoked token | 401 Unauthorized | ✅ |
| Revoke all tokens | All tokens revoked | ✅ |
| Invalid token | 401 Unauthorized | ✅ |
| Missing token | 400 Bad Request | ✅ |
Troubleshooting
Issue: "Cannot find module '@nestjs/schedule'"
Solution: The TokenCleanupService is optional. The system works without it. See OPTIONAL_SETUP.md if you want to enable automatic cleanup.
Issue: "Invalid refresh token" immediately after login
Possible Causes:
- Token not being stored in database
- Hash mismatch
- Database connection issue
Debug:
-- Check if token was created
SELECT * FROM refresh_tokens ORDER BY "createdAt" DESC LIMIT 1;
Issue: Tokens not being revoked
Debug:
-- Check if isRevoked is being set
SELECT "isRevoked", COUNT(*) FROM refresh_tokens GROUP BY "isRevoked";
Issue: Database bloat
Solution: Run manual cleanup:
DELETE FROM refresh_tokens WHERE "expiresAt" < NOW();
DELETE FROM refresh_tokens WHERE "isRevoked" = true;
Or enable automatic cleanup (see OPTIONAL_SETUP.md).
Production Testing Checklist
Before deploying to production:
- All test scenarios pass
- Token rotation works correctly
- Tokens are stored as hashes
- Foreign key constraints work
- Expired tokens are rejected
- Revoked tokens are rejected
- Rate limiting is configured (optional)
- HTTPS is enforced
- Logging is in place
- Database backup is configured
- Cleanup strategy is decided (automatic or manual)
Performance Benchmarks
Expected performance (adjust based on your infrastructure):
- Login: < 200ms
- Refresh: < 150ms
- Logout: < 100ms
- Token validation: < 50ms
- Database query: < 20ms
Monitor these metrics in production and optimize as needed.