Re-open Meta's page selection on every connect with auth_type=rerequest - #314
Merged
Conversation
FacebookProvider already exposes reRequest(), which sets exactly the auth_type=rerequest field the connect flows need. Calling it directly replaces with(['auth_type' => 'rerequest']) and drops the comment the raw parameter needed, since the method name carries the intent. It also avoids a footgun: AbstractProvider::with() assigns $parameters rather than merging into them, so a second with() anywhere in the chain would have silently dropped auth_type. Cover InstagramFacebookController@connect, which had no test at all, so both halves of the fix are verified rather than just the Facebook one.
TikTok skips the authorization page whenever the browser holds a valid session and the app was authorized before, handing back a code for whoever happens to be logged in. A user who wants to connect a different account never reaches the account chooser; the only way out is logging out of TikTok in the browser first. Same class of trap as Meta reusing its page selection, different mechanism. Login Kit documents disable_auto_auth for this: 0 skips the page for valid sessions, 1 always shows it. redirectToProvider() had no way to pass extra authorize-URL parameters, so it takes an optional array now. It only calls with() when that array is non-empty, leaving the Discord, Pinterest and X redirects byte for byte as they were.
…hod in SocialController. This change cleans up the code by eliminating unnecessary comments that no longer apply to the method's implementation.
with() assigns $parameters rather than merging, and nothing else ever populates them, so with([]) is a no-op and the guard around it bought nothing. Dropping it restores the fluent chain the method had before. The Discord, Pinterest and X connect tests mock the driver whole, so they need the call stubbed even though the array stays empty.
prompt=consent reopens the consent screen but never the account chooser, so a browser holding one Google session authorizes that account every time. channels?mine=true is scoped to whichever identity minted the token, so a Brand Account channel under a different Google account is absent from the response rather than merely unpicked, and the callback connects channels[0] regardless. Google documents prompt as a space-delimited list, and select_account and consent are distinct values, so the two compose. The connect test stubbed with() without constraining its argument, which left every redirect parameter unasserted; it now pins the whole array.
Instagram reuses the browser session on a repeat connect, so a user logged in as one account cannot reach another; the callback answers wrong_account with no way forward. Business Login documents force_reauth for exactly this, against www.instagram.com/oauth/authorize, which is the host this provider already targets. The parameter goes in the provider rather than through with(), because getAuthUrl() builds the query by hand and never merges $parameters. The new test drives the real provider through the connect route instead of mocking Socialite, since the URL is assembled below the controller.
paulocastellano
force-pushed
the
pr/meta-rerequest
branch
from
September 8, 2026 15:40
8201a85 to
9d5341c
Compare
paulocastellano
added a commit
that referenced
this pull request
Sep 8, 2026
The mocked connect tests prove reRequest() and with() were called, not that anything lands in the URL Socialite builds. If a Socialite upgrade changed what reRequest() emits, the mocks would stay green and the bug those parameters fix would come back unnoticed. These drive the real provider through the connect route instead, and assert on the parameter rather than the whole URL, so a version bump in the vendor's authorize host does not break them. Verified by removing each parameter in turn and confirming its test fails: auth_type=rerequest (Facebook, Instagram-Facebook), disable_auto_auth=1 (TikTok), prompt=select_account consent (YouTube). Instagram already had one from #314.
paulocastellano
added a commit
that referenced
this pull request
Sep 8, 2026
…#345) * Make the social connect tests assert where the redirect actually goes Every connect test sent X-Inertia: true and asserted a 409. That status was never evidence of anything: the request carries no X-Inertia-Version, so Inertia treats it as a version mismatch and answers 409 with X-Inertia-Location pointing back at the same URL. Any error inside the controller produced the same 409, and the assertion passed regardless. Proof of how empty this was: dropping a throw into redirectToProvider, right after the session is remembered, left every one of these tests green. Without the header, Inertia::location returns a plain redirect, so the destination is assertable. Socialite-mocked flows use assertRedirect against the mocked target; Threads and Mastodon build their URL by hand, so those assert the authorize endpoint they are supposed to reach. Verified by breaking each connect path in turn and confirming the tests that cover it fail: redirectToProvider (Discord, Pinterest, TikTok, X), the seven controllers with their own connect method, and Mastodon's authorize action, which the connect probe does not reach. No production code changes. * Assert the connect parameters reach the authorize URL The mocked connect tests prove reRequest() and with() were called, not that anything lands in the URL Socialite builds. If a Socialite upgrade changed what reRequest() emits, the mocks would stay green and the bug those parameters fix would come back unnoticed. These drive the real provider through the connect route instead, and assert on the parameter rather than the whole URL, so a version bump in the vendor's authorize host does not break them. Verified by removing each parameter in turn and confirming its test fails: auth_type=rerequest (Facebook, Instagram-Facebook), disable_auto_auth=1 (TikTok), prompt=select_account consent (YouTube). Instagram already had one from #314. * Cover the remaining connect flows against the real authorize URL Discord, Pinterest, X and LinkedIn were still tested only through a Socialite mock, which compares the fixture against itself and says nothing about the URL the app builds. That the fixture drifts is not hypothetical: the X mock returns https://twitter.com/i/oauth2/authorize while the provider has been emitting https://x.com/i/oauth2/authorize, and no test noticed. Each of these pins what the flow depends on: Discord's bot scope and permission bitfield, plus the absence of disable_guild_select, which would silence the server picker the same way Meta used to silence the page picker; X's PKCE challenge and offline.access, without which there is no refresh token; the Pinterest and LinkedIn scopes that publishing needs. Verified by breaking each one and confirming its test fails: the bot scope in DiscordProvider, offline.access in XController, pins:write in PinterestController, and the organization scopes in the LinkedIn config.
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.
The trap
Create a new Facebook Page, hit "Connect Facebook" — and TryPost silently connects the old Page again. No page picker, no way to reach the new Page from inside the app.
Meta's OAuth dialog reuses the previous grant, including its page selection: once the app has been authorized for one Page, subsequent logins skip the consent screen entirely and
/me/accountskeeps returning only the originally ticked Pages. Disconnecting in TryPost doesn't help, because the grant lives on Meta's side. The only escape is manual surgery in facebook.com → Settings → Business Integrations, which users find only after filing a support ticket (ours did).The fix
Pass
auth_type=rerequeston the OAuth redirect for both Meta flows (Facebook and Instagram-via-Facebook). Meta then re-opens the consent dialog on every connect, with the "Edit settings" page selection available, so adding a newly created Page is just: connect again → tick the Page.The cost is one extra consent screen for users reconnecting an unchanged setup — which is also the screen that tells them what they previously granted, so it's arguably a feature.
Testing
FacebookControllerTest/InstagramFacebookControllerTestpass, with the connect-redirect mock asserting the new parameter. Verified live: withrerequestin place, a freshly created Page shows up in the page selection dialog on the very next connect.