# Testing Refresh Token System This guide provides step-by-step instructions to test the refresh token implementation. ## Prerequisites 1. Database is running and migrations are applied: ```bash npm run migration:run ``` 2. Application is running: ```bash npm run start:dev ``` 3. Seed data is loaded (includes admin user): ```bash npm run seed ``` ## Test Scenarios ### 1. Test User Login (Get Tokens) **Request**: ```bash curl -X POST http://localhost:3000/api/auth/login \ -H "Content-Type: application/json" \ -d '{ "email": "admin@retailpos.com", "password": "Admin123!" }' ``` **Expected Response**: ```json { "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**: ```bash # Save for next tests export ACCESS_TOKEN="" export REFRESH_TOKEN="" ``` --- ### 2. Test Access Token Works Use the access token to access a protected endpoint: **Request**: ```bash curl -X GET http://localhost:3000/api/auth/profile \ -H "Authorization: Bearer $ACCESS_TOKEN" ``` **Expected Response**: ```json { "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**: ```bash curl -X POST http://localhost:3000/api/auth/refresh \ -H "Content-Type: application/json" \ -d "{ \"refreshToken\": \"$REFRESH_TOKEN\" }" ``` **Expected Response**: ```json { "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_token` is different from old one - ✅ New `refresh_token` is different from old one - ✅ HTTP status is 200 - ✅ User details are returned **Save new tokens**: ```bash export NEW_ACCESS_TOKEN="" export NEW_REFRESH_TOKEN="" ``` --- ### 4. Test Token Rotation (Old Token Should Be Revoked) Try to use the OLD refresh token again: **Request**: ```bash curl -X POST http://localhost:3000/api/auth/refresh \ -H "Content-Type: application/json" \ -d "{ \"refreshToken\": \"$REFRESH_TOKEN\" }" ``` **Expected Response**: ```json { "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**: ```bash curl -X POST http://localhost:3000/api/auth/logout \ -H "Content-Type: application/json" \ -d "{ \"refreshToken\": \"$NEW_REFRESH_TOKEN\" }" ``` **Expected Response**: ```json { "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**: ```bash curl -X POST http://localhost:3000/api/auth/refresh \ -H "Content-Type: application/json" \ -d "{ \"refreshToken\": \"$NEW_REFRESH_TOKEN\" }" ``` **Expected Response**: ```json { "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**: ```bash curl -X POST http://localhost:3000/api/auth/login \ -H "Content-Type: application/json" \ -d '{ "email": "admin@retailpos.com", "password": "Admin123!" }' ``` Save the tokens: ```bash export ACCESS_TOKEN="" export REFRESH_TOKEN="" ``` **Step 2 - Revoke All Tokens**: ```bash curl -X POST http://localhost:3000/api/auth/revoke-all \ -H "Authorization: Bearer $ACCESS_TOKEN" ``` **Expected Response**: ```json { "success": true, "message": "All refresh tokens revoked successfully" } ``` **Step 3 - Verify Token is Revoked**: ```bash 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 ```bash 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 ```bash 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: ```bash # 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**: ```sql 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**: ```sql SELECT COUNT(*) as active_tokens FROM refresh_tokens WHERE "isRevoked" = false AND "expiresAt" > NOW(); ``` **Query 3 - Tokens Per User**: ```sql 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: ```bash # 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**: 1. Token not being stored in database 2. Hash mismatch 3. Database connection issue **Debug**: ```sql -- Check if token was created SELECT * FROM refresh_tokens ORDER BY "createdAt" DESC LIMIT 1; ``` ### Issue: Tokens not being revoked **Debug**: ```sql -- Check if isRevoked is being set SELECT "isRevoked", COUNT(*) FROM refresh_tokens GROUP BY "isRevoked"; ``` ### Issue: Database bloat **Solution**: Run manual cleanup: ```sql 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.