Files
retail-nest/TESTING_REFRESH_TOKEN.md
2025-10-21 16:30:18 +07:00

10 KiB

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:

    npm run migration:run
    
  2. Application is running:

    npm run start:dev
    
  3. 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_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:

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:

  1. Token not being stored in database
  2. Hash mismatch
  3. 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.