fix(auth): accept refresh responses without refresh_token - #5
Merged
Merged
Conversation
Launchpad's legacy `type=refresh` response carries only `access_token` (see the "legacy format refresh" case in basecamp/basecamp-sdk). The client required `refresh_token` and `expires_in` as well, so every refresh failed with "BC4 token exchange failed (status=200)" once the first access token expired, and the integration needed a manual re-authorization every two weeks. Only `access_token` is required now. Without a rotated refresh_token the current one is kept; without `expires_in` the documented two-week lifetime is assumed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Every token refresh fails with
BC4 token exchange failed (status=200)as soon as the first access token has expired. Launchpad answers the legacytype=refreshrequest with only anaccess_token, butOAuth2Authentication::exchangeRefreshToken()also requiredrefresh_tokenandexpires_in. An integration therefore stopped working two weeks after the initial authorization and needed a manual re-authorization each time.The response shape matches Basecamp's own SDK: its test case "legacy format refresh" in basecamp/basecamp-sdk returns
{"access_token": "..."}only, and the Python SDK readsrefresh_tokenandexpires_inas optional fields.Change
access_tokenis required in the refresh response.refresh_token, the current one is kept and passed back to the storage, soTokenStorageInterfacestays unchanged.expires_in, the documented two-week lifetime (1209600 s) is assumed.Tests
testRefreshWithoutRotationKeepsCurrentRefreshToken: access-token-only response keeps the current refresh token and defaultsexpires_in.testRefreshResponseWithoutAccessTokenFails: a response withoutaccess_tokenstill fails.docker compose run --rm php: 23 tests, 57 assertions, all green.🤖 Generated with Claude Code