From 9353ae66565511a704642a747f7b9b966b7f23dd Mon Sep 17 00:00:00 2001 From: Piotr Siuszko Date: Wed, 21 Jan 2026 15:33:56 +0100 Subject: [PATCH 1/2] Add Federation Identity documentation --- docs/cli/guides/ms-federation.md | 76 +++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/docs/cli/guides/ms-federation.md b/docs/cli/guides/ms-federation.md index 5fc79aca..8526bfc8 100644 --- a/docs/cli/guides/ms-federation.md +++ b/docs/cli/guides/ms-federation.md @@ -49,6 +49,80 @@ The `MySample` class included as the generic type in the `IFederatedLogin` inter [FederationId("myId")] public class MySample : IFederationId { } ``` + +### Federated Login + +This feature allows you to implement OAuth2, OpenID Connect, or custom external authentication providers within Beamable. It also supports two-way, challenge-based flows for PKI-based authentication, commonly used in Web3/blockchain scenarios. + +#### Use Cases + +- **Blockchain Wallet Authentication**: Attach a wallet to a player's account and use it for secure authentication. +- **External Provider Integration**: Integrate with providers like **Auth0** to provide a Single Sign-On (SSO) experience for your game. + +#### Implementation + +To add support for federated login, implement the `IFederatedLogin` interface. This interface requires the implementation of the `Authenticate` method, which is used to verify a player's identity via the federated provider. This method supports both standard single-step authentication and two-step challenge-response flows. + +It can be used for 2 step verification as well as regular authentication. + +!!! 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). + +#### Simple Implementation (Single-Step) + +This implementation assumes the external provider gives a token that can be validated immediately. + +```csharp +public async Promise Authenticate(string token, string challenge, string solution) +{ + var externalId = await CallExternalAuthProvider(token); + return new FederatedAuthenticationResponse { user_id = externalId }; +} + +async Promise CallExternalAuthProvider(string token) +{ + // Call external auth provider API here + return token; +} +``` + +#### Two-Step Verification (Challenge-Response) + +For scenarios like [Solana/Phantom wallet authentication](https://github.com/beamable/solana-example/tree/develop/Packages/com.beamable.solana/Runtime/BeamableServices~/SolanaFederation), you must issue a challenge for the client to sign. + +```csharp +public 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 Promise.Successful(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 Promise.Successful(new FederatedAuthenticationResponse + { + challenge = $"Please sign this random message to authenticate, {Guid.NewGuid()}", + challenge_ttl = Configuration.AuthenticationChallengeTtlSec + }); +} +``` + --- ## CLI Commands @@ -138,4 +212,4 @@ The following IFederationId must be annotated with a FederationIdAttribute with public interface MyFederation : IFederationId {} ``` ---- \ No newline at end of file +--- From 508e44c069ff827709af37e39550ed40ad45138c Mon Sep 17 00:00:00 2001 From: Piotr Siuszko Date: Thu, 22 Jan 2026 13:37:59 +0100 Subject: [PATCH 2/2] Update ms-federation.md --- docs/cli/guides/ms-federation.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/cli/guides/ms-federation.md b/docs/cli/guides/ms-federation.md index 8526bfc8..a9d71bf0 100644 --- a/docs/cli/guides/ms-federation.md +++ b/docs/cli/guides/ms-federation.md @@ -92,7 +92,7 @@ async Promise CallExternalAuthProvider(string token) For scenarios like [Solana/Phantom wallet authentication](https://github.com/beamable/solana-example/tree/develop/Packages/com.beamable.solana/Runtime/BeamableServices~/SolanaFederation), you must issue a challenge for the client to sign. ```csharp -public Promise Authenticate(string token, string challenge, string solution) +public async Promise Authenticate(string token, string challenge, string solution) { if (string.IsNullOrEmpty(token)) { @@ -105,8 +105,8 @@ public Promise Authenticate(string token, strin // Verify the solution if (AuthenticationService.IsSignatureValid(token, challenge, solution)) // User identity is confirmed - return Promise.Successful(new FederatedAuthenticationResponse - { user_id = token }); + 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, @@ -115,11 +115,11 @@ public Promise Authenticate(string token, strin } // Generate a challenge - return Promise.Successful(new FederatedAuthenticationResponse + return new FederatedAuthenticationResponse { challenge = $"Please sign this random message to authenticate, {Guid.NewGuid()}", challenge_ttl = Configuration.AuthenticationChallengeTtlSec - }); + }; } ```