Skip to content

feat(auth): token refresh retry on 401 responses#111

Open
trungvose wants to merge 10 commits intomainfrom
feature/token-refresh-on-401
Open

feat(auth): token refresh retry on 401 responses#111
trungvose wants to merge 10 commits intomainfrom
feature/token-refresh-on-401

Conversation

@trungvose
Copy link
Copy Markdown
Owner

Summary

  • Added tryRefreshToken() public method to AuthStore that refreshes the Spotify access token, persists new tokens to localStorage and store state, and returns the new access token
  • Rewrote UnauthorizedInterceptor to silently refresh expired tokens on 401 responses (up to 2 attempts) and replay the original failed request with the fresh token
  • Token expired modal now only appears after both refresh attempts fail — previously it showed immediately on any 401

Flow

Request → API → 401 response
  │
  ├─ Is a refresh already in progress?
  │    ├─ YES → wait for it to complete, get new token, replay request
  │    └─ NO  → attempt 1:
  │              │
  │              ├─ authStore.tryRefreshToken() succeeds
  │              │    → replay original request with new Bearer token
  │              │
  │              └─ fails → attempt 2:
  │                   ├─ succeeds → replay original request
  │                   └─ fails → uiStore.showUnauthorizedModal()
  │                        → return error

Test plan

  • 5 unit tests for AuthStore.tryRefreshToken() (success, localStorage persistence, state update, missing token error, API error propagation)
  • 8 unit tests for UnauthorizedInterceptor (passthrough, non-401 errors, skip URLs, refresh+replay, 2 failures then modal, second attempt success, concurrent 401 coalescing, concurrent failure propagation)
  • Build succeeds with no compile errors
  • Manual: clear access_token from localStorage while app is running → verify silent refresh
  • Manual: clear both access_token and refresh_token → verify modal appears

🤖 Generated with Claude Code

trungvose and others added 10 commits April 11, 2026 15:47
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds public tryRefreshToken() method that wraps the existing private
refreshAccessToken() with token persistence and state updates, then
returns the new access token string for use by the upcoming
UnauthorizedInterceptor. Also configures jest-preset-angular for the
web-auth-data-access library so Angular testing utilities work correctly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The Spotify Web Playback SDK calls getOAuthToken multiple times (on
expiry, reconnect, wake from sleep). Previously the callback captured
a static token snapshot, so after a refresh the SDK kept using the
stale token, causing authentication_error and NO_ACTIVE_DEVICE on
play. Now reads the latest token from localStorage each time.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Moves the LOCALSTORAGE_KEYS constant from auth.store.ts (private) to
local-storage.service.ts (exported) so both AuthStore and
PlaybackService can reference keys without hardcoded strings or
circular dependencies.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Per spotify/web-api#1325, passing device_id avoids 404 "No active
device found" errors when Spotify loses track of the active device.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PlayerApiService now stores the deviceId from transferUserPlayback()
and automatically appends it as a query param on play() calls.
Per spotify/web-api#1325, this avoids 404 "No active device found".

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@trungvose trungvose force-pushed the feature/token-refresh-on-401 branch from 99f23a0 to 187a198 Compare April 11, 2026 15:08
@trungvose trungvose added the wip label Apr 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant