I think the best way to understand how this pattern of OAuth use can be insecure is by talking about the purpose of OAuth scopes.
According to its design, an OAuth scope represents a set of possible actions that can be performed on behalf of the authenticated user. So (as a made-up example) if your client gets a token scope with the "send_messages"
scope, then it can send messages on behalf of the user. In some sense, the true definition of a scope is what actions a token with that scope lets you perform.
The problem with the way "Login with FB/Twitter/etc." is implemented is that they all use a single scope (e.g. "public_profile"
for Facebook).
According to the documentation, this scope is pretty harmless - with this the client can view the identity of the current user. However, if a third-party website A.com uses this token to check a user's identity and log them in to A.com, they have essentially expanded the definition of the public_profile
scope to mean "view public profile information, and also access this user's resources on A.com".
All you need at this point is for an attacker to get a token with a public_profile
scope (for example, by running another website with a "Log in with Facebook" link) - this requested scope seems harmless according to Facebook's documentation and what they show the user, but in reality if the attacker can pass that to A.com, they can access the user's resources.
The solution
The solution that has emerged is for A.com to verify that the token was generated specifically for A.com - that is, that during the OAuth login flow, the user was directed back to A.com instead of another site.
To do this, the generated OAuth token has extra information associated with it, specifically "which website/application was this token sent to". The presence of this extra information determines the result when you inspect the token (e.g. the /debug_token endpoint).
(I personally think this more or less constitutes a "invisible scope", and we wouldn't have this confusion if Facebook/etc. had provided custom third-party login scopes, e.g. "third-party:a.com"
- however, the existing solution is just as functional.)
(The "implicit" flow of OAuth sometimes gets wholly blamed in this situation, which I think is missing half the point. The issue is that the "implicit" flow allows an attacker to insert their own OAuth tokens by intercepting the browser's redirect, but this would not be a problem if the OAuth scopes were not being redefined.)