Skip to content

Fix 401 authentication errors with automatic token refresh#25

Merged
eman merged 3 commits intomainfrom
fix-token-refresh-401
Oct 24, 2025
Merged

Fix 401 authentication errors with automatic token refresh#25
eman merged 3 commits intomainfrom
fix-token-refresh-401

Conversation

@eman
Copy link
Copy Markdown
Owner

@eman eman commented Oct 24, 2025

Problem

The CLI was failing with 401 Unauthorized errors when using cached tokens. When tokens were rejected by the server but could still be refreshed, users had to manually delete the cached tokens file or re-enter their credentials.

Solution

This PR implements automatic token refresh on 401 errors with the following improvements:

Changes Made

  1. API Client ()

    • Added automatic token refresh when receiving 401 Unauthorized responses
    • Modified _make_request() to detect 401 errors and attempt token refresh
    • Implemented single-retry logic to prevent infinite loops
    • Logs warning when attempting refresh for visibility
  2. Auth Client ()

    • Enhanced refresh_token() to preserve AWS credentials from old tokens
    • Refresh responses don't include AWS credentials (access_key_id, secret_key, session_token)
    • Copies AWS credentials from old tokens to new tokens
    • Ensures MQTT client can connect after token refresh
  3. CLI Main ()

    • Added logic to save refreshed tokens after successful API calls
    • Ensures updated tokens persist to cache for future runs
    • Tokens saved after get_first_device() which may trigger refresh

How It Works

  1. CLI loads cached tokens from ~/.nwp500_tokens.json
  2. API request made with cached tokens
  3. If server returns 401, system automatically:
    • Logs warning about refresh attempt
    • Calls refresh_token() with cached refresh token
    • Preserves AWS credentials in new tokens
    • Retries original request with new access token
  4. Updated tokens saved back to cache
  5. Future runs use refreshed tokens

Testing

  • ✅ All 40 existing tests pass
  • ✅ Type checking: No errors
  • ✅ Linting: All checks passed
  • ✅ Manual testing: Successfully refreshed expired tokens and completed operations
  • ✅ Manual testing: Verified cached tokens work correctly after refresh

Benefits

  • No more manual token cache deletion
  • No need to re-enter credentials when tokens expire
  • Seamless user experience
  • Maintains MQTT connectivity after token refresh

- Add automatic token refresh on 401 Unauthorized responses in API client
- Preserve AWS credentials when refreshing tokens (required for MQTT)
- Save refreshed tokens to cache after successful API calls
- Add retry logic to prevent infinite retry loops

This fix addresses the issue where cached tokens were rejected by the
server with 401 errors but could still be refreshed. Previously, users
would need to manually delete cached tokens or re-enter credentials.
Now the system automatically refreshes tokens and retries the request.

Fixes the 'API request failed: 401' error when using cached tokens.
@eman eman requested a review from Copilot October 24, 2025 01:00
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR implements automatic token refresh on 401 authentication errors to prevent users from having to manually delete cached tokens or re-enter credentials when tokens expire.

Key changes:

  • API client automatically detects 401 errors and refreshes tokens with single-retry logic
  • Auth client preserves AWS credentials during token refresh (required for MQTT connectivity)
  • CLI saves refreshed tokens back to cache for future use

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
src/nwp500/api_client.py Added automatic token refresh on 401 errors with retry logic to prevent infinite loops
src/nwp500/auth.py Enhanced refresh_token() to preserve AWS credentials from old tokens since refresh responses don't include them
src/nwp500/cli/__main__.py Added logic to persist refreshed tokens to cache after API calls

- Catch only TokenRefreshError and AuthenticationError instead of Exception
- Prevents masking unexpected errors during token refresh
- Addresses code review feedback
@eman eman force-pushed the fix-token-refresh-401 branch from cf11f74 to 3846d80 Compare October 24, 2025 01:13
@eman
Copy link
Copy Markdown
Owner Author

eman commented Oct 24, 2025

✅ Fixed in commit 3846d80

Changed the exception handler from catching all Exception types to specifically catching:

  • TokenRefreshError - raised when token refresh fails
  • AuthenticationError - base class for authentication-related errors

This ensures we only catch expected authentication errors and don't mask unexpected errors during token refresh.

@eman eman requested a review from Copilot October 24, 2025 01:15
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

)
try:
# Try to refresh the token
if self._auth_client.current_tokens:
Copy link

Copilot AI Oct 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing null check for refresh_token. If current_tokens exists but refresh_token is None/empty, this will pass None to refresh_token() which may cause unexpected behavior. Add validation: if self._auth_client.current_tokens and self._auth_client.current_tokens.refresh_token:

Suggested change
if self._auth_client.current_tokens:
if (
self._auth_client.current_tokens
and self._auth_client.current_tokens.refresh_token
):

Copilot uses AI. Check for mistakes.
Comment on lines +403 to +404
# Preserve AWS credentials from old tokens if not in refresh
# response
Copy link

Copilot AI Oct 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment formatting issue: multi-line comment should use proper continuation or be reformatted as a single line.

Suggested change
# Preserve AWS credentials from old tokens if not in refresh
# response
# Preserve AWS credentials from old tokens if not in refresh response

Copilot uses AI. Check for mistakes.
- Check that refresh_token exists and is not None/empty
- Use local variable to avoid repeated attribute access
- Log error when refresh_token is not available
- Prevents passing None to refresh_token() method

Addresses code review feedback
@eman
Copy link
Copy Markdown
Owner Author

eman commented Oct 24, 2025

✅ Fixed in commit f87ab65

Added validation to check that refresh_token is not None/empty before attempting to refresh:

tokens = self._auth_client.current_tokens
if tokens and tokens.refresh_token:
    await self._auth_client.refresh_token(tokens.refresh_token)
    # ... retry logic
else:
    _logger.error("Cannot refresh token: refresh_token not available")

Benefits:

  • Prevents passing None to refresh_token() method
  • Provides clear error message when refresh_token is unavailable
  • Uses local variable to avoid repeated attribute access (cleaner code)
  • Falls through to raise original 401 error when refresh is not possible

@eman eman merged commit 9940178 into main Oct 24, 2025
10 checks passed
@eman eman deleted the fix-token-refresh-401 branch October 24, 2025 01:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants