From 54f2a8ae3d5c241c303f99a492dc5a2fe9d43041 Mon Sep 17 00:00:00 2001 From: MevLyshkin Date: Fri, 30 Jan 2026 16:46:19 +0100 Subject: [PATCH] Federated identity docs improvements --- .../identity/federated-identity.md | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/docs/unity/user-reference/beamable-services/identity/federated-identity.md b/docs/unity/user-reference/beamable-services/identity/federated-identity.md index d9b3eb316..0fca1e33b 100644 --- a/docs/unity/user-reference/beamable-services/identity/federated-identity.md +++ b/docs/unity/user-reference/beamable-services/identity/federated-identity.md @@ -36,7 +36,7 @@ Let's say you already have a TunaService that holds all your user's data. We wil ```csharp [Microservice("AuthenticationMicroservice")] -public class AuthenticationMicroservice : IFederatedLogin, IFederatedLogin +public class AuthenticationMicroservice : IFederatedLogin, IFederatedLogin, Microservice { public async Promise IFederatedLogin.Authenticate(string token, string challenge, string solution) { @@ -56,8 +56,46 @@ public class AuthenticationMicroservice : IFederatedLogin, IF } ``` +!!! info "User Id generation" + + Ensure that User IDs are generated deterministically. For a given input from the provider, the resulting User ID must always be the same. Avoid using `Guid.NewGuid().ToString()`, as this creates a new account for every login attempt. Instead, use a unique identifier provided by the external auth provider (e.g., a subject ID or public key). + + In this example, we didn't use the "challenge" and "solution" arguments. The standard use case for challenges is wallet authentication. If a client sends us a wallet address as a token, the only way to verify the ownership of that wallet is to issue a challenge, and require a user to sign that challenge using their private key. [Solana/Phantom wallet authentication](https://github.com/beamable/solana-example) is an example that uses a challenge. + +```csharp +public async Promise Authenticate(string token, string challenge, string solution) +{ + if (string.IsNullOrEmpty(token)) + { + BeamableLogger.LogError("We didn't receive a token (public key)"); + throw new InvalidAuthenticationRequest("Token (public key) is required"); + } + + if (!string.IsNullOrEmpty(challenge) && !string.IsNullOrEmpty(solution)) + { + // Verify the solution + if (AuthenticationService.IsSignatureValid(token, challenge, solution)) + // User identity is confirmed + return new FederatedAuthenticationResponse + { user_id = token }; + // Signature is invalid, user identity isn't confirmed + BeamableLogger.LogWarning( + "Invalid signature {signature} for challenge {challenge} and wallet {wallet}", solution, + challenge, token); + throw new UnauthorizedException(); + } + + // Generate a challenge + return new FederatedAuthenticationResponse + { + challenge = $"Please sign this random message to authenticate, {Guid.NewGuid()}", + challenge_ttl = Configuration.AuthenticationChallengeTtlSec + }; +} +``` + ### Attaching an external identity to a player (CLIENT) ```csharp