531 lines
10 KiB
Markdown
531 lines
10 KiB
Markdown
# 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="<your-access-token>"
|
|
export REFRESH_TOKEN="<your-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="<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**:
|
|
```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="<access-token>"
|
|
export REFRESH_TOKEN="<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.
|