Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Send client-generated session GUID for identification purposes #28892

Merged
merged 2 commits into from
Jul 17, 2024

Conversation

bdach
Copy link
Collaborator

@bdach bdach commented Jul 17, 2024

This is the first half of a change that may fix #26338 (it definitely fixes one case where the issue happens, but I'm not sure if it will cover all of them).

As described in the issue thread, using the jti claim from the JWT used for authorisation seemed like a decent idea. However, upon closer inspection the scheme falls over badly in a specific scenario where:

  1. A client instance connects to spectator server using JWT A.

  2. At some point, JWT A expires, and is silently rotated by the game in exchange for JWT B.

    The spectator server knows nothing of this, and continues to only track JWT A, including the old jti claim in said JWT.

  3. At some later point, the client's connection to one of the spectator server hubs drops out. A reconnection is automatically attempted, but it is attempted using JWT B.

    The spectator server was not aware of JWT B until now, and said JWT has a different jti claim than the old one, so to the spectator server, it looks like a completely different client connecting, which boots the user out of their account.

This PR adds a per-session GUID which is sent in a HTTP header on every connection attempt to spectator server. This GUID will be used instead of the jti claim in JWTs as a persistent identifier of a single user's single lazer session, which bypasses the failure scenario described above.

I don't think any stronger primitive than this is required. As far as I can tell this is as strong a protection as the JWT was (which is to say, not very strong), and doing this removes a lot of weird complexity that would be otherwise incurred by attempting to have client ferry all of its newly issued JWTs to the server so that it can be aware of them.

Testing this in a scenario that has at least a sliver of resemblance to the real world can be performed using the instructions in #26338 (comment).

This is the first half of a change that *may* fix
ppy#26338 (it definitely fixes *one case*
where the issue happens, but I'm not sure if it will cover all of them).

As described in the issue thread, using the `jti` claim from the JWT
used for authorisation seemed like a decent idea. However, upon closer
inspection the scheme falls over badly in a specific scenario where:

1. A client instance connects to spectator server using JWT A.

2. At some point, JWT A expires, and is silently rotated by the game in
   exchange for JWT B.

   The spectator server knows nothing of this, and continues to only
   track JWT A, including the old `jti` claim in said JWT.

3. At some later point, the client's connection to one of the spectator
   server hubs drops out. A reconnection is automatically attempted,
   *but* it is attempted using JWT B.

   The spectator server was not aware of JWT B until now, and said JWT
   has a different `jti` claim than the old one, so to the spectator
   server, it looks like a completely different client connecting, which
   boots the user out of their account.

This PR adds a per-session GUID which is sent in a HTTP header on every
connection attempt to spectator server. This GUID will be used instead
of the `jti` claim in JWTs as a persistent identifier of a single user's
single lazer session, which bypasses the failure scenario described
above.

I don't think any stronger primitive than this is required. As far as I
can tell this is as strong a protection as the JWT was (which is to say,
not *very* strong), and doing this removes a lot of weird complexity
that would be otherwise incurred by attempting to have client ferry all
of its newly issued JWTs to the server so that it can be aware of them.
@@ -19,6 +19,9 @@ public class HubClientConnector : PersistentEndpointClientConnector, IHubClientC
{
public const string SERVER_SHUTDOWN_MESSAGE = "Server is shutting down.";

public const string VERSION_HASH_HEADER = @"OsuVersionHash";
Copy link
Member

Choose a reason for hiding this comment

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

Any reason for not making this X-Osu-Version-Hash? I think the X- prefix is pretty standard for custom headers.

Copy link
Collaborator Author

@bdach bdach Jul 17, 2024

Choose a reason for hiding this comment

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

This header is already being sent and received, so mostly the reason is not wanting to faff about with

  • sending this header under 2 header names in the interim
  • updating the server side to read the new one
  • waiting a bit so that the old header name can be deleted from client

If you're fine with said faffery (or a hard break) I can go do it.

Copy link
Member

Choose a reason for hiding this comment

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

I'd just send under both for now and we can figure what to do about it in the future. Remove old at soem point.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@peppy peppy merged commit 99c5025 into ppy:master Jul 17, 2024
8 of 17 checks passed
@bdach bdach deleted the better-client-identifier branch July 18, 2024 12:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Unexpected logout caused by network reconnection
2 participants