Skip to main content

Authentication

A player signs in once, at startup, from the Game Instance. Every method, email and password, a one-time link by email, a social provider, converges on one pipeline: the sign-in returns an identity session token, the SDK mints a short-lived app-scoped token from it, adopts the app's endpoints, fires a success event, and requests the realtime connection with the app token. Your code picks the method and reacts to the event; the tokens are the SDK's business.

When you touch this​

Once per project for the sign-in itself, which the Quickstart already did with a password. Again when you add a sign-in screen (which methods to offer, restoring a saved session), when a player signs out, and when you let a player link a second way to sign in.

Two tokens​

TokenPlaneWho holds itLifetime
Identity session tokenManagement: the account, Studio, linked identitiesThe SDK persists it so a later launch can restore the sessionUntil sign-out
App-scoped tokenGameplay: the Game API and the realtime connection, confined to one appThe SDK keeps it in memory onlyAbout 30 minutes; refreshed for you before expiry, and again on a realtime TOKEN_EXPIRED

The session token is rejected for gameplay and the app token cannot manage the account, so neither can stand in for the other. You never handle either: the SDK stores, mints, refreshes, and installs them. The mint itself is described on Portals and app-scoped tokens; the sign-in calls underneath are on Sign in.

Never log, print, persist, or display a token.

FCrowdyAuthResult::GameToken, despite its name, is the identity session token, and the SDK already holds it. The SDK's own On Login and On Register events on the Crowdy SDK Subsystem carry a human-readable message and never a credential, because anything on a Blueprint-assignable event can end up on screen. Keep yours the same way: read UserID, ignore GameToken.

Choosing a method​

MethodNeedsCall
Email and passwordNothing but the two strings. No inbox, no browser. Permanent and first-class, not a legacy path.Login, or Register for a new account
Magic linkAn inbox the player can reach while the game runsBeginMagicLinkSignIn
Social providerThe system browser, and a provider the server has enabledBeginSocialSignIn
A saved sessionA previous sign-in on this machineRestoreSession

Password is the right default for a build machine, a bot, or an agent, and for any flow that must complete without leaving the game.

Two surfaces, one pipeline​

UCrowdySDKSubsystem (Game Instance subsystem, category CrowdySDK, Authentication) is the simple surface the Quickstart uses: Login(Email, Password), Register(Email, Password), CompleteLoginLink(Token), BeginMagicLinkSignIn(Email), BeginSocialSignIn(Provider), RefreshAppToken(), Logout(), and RequestUDPAccess(). Each forwards to the rich surface below with no per-call delegate, and the outcome arrives on the subsystem's multicast events: OnLogin and OnRegister (FOnLogin, FOnRegister: bSuccess, Message) and OnLogout (FOnLogout: bSuccess).

UCrowdyAuthentication (Game Instance subsystem, Crowdy Authentication, category Crowdy SDK, Authentication) is the rich surface: every call takes per-call delegates, FOnAuthSuccess with an FCrowdyAuthResult and FOnAuthError with a message, and every success also broadcasts an event carrying the result.

CallWhat it does
Login(Email, Password, OnSuccess, OnError)Password sign-in. Persists the session so a later launch can restore it.
Register(Email, Password, OnSuccess, OnError)Creates the account and signs in. An address that already has an account is refused with EMAIL_ALREADY_REGISTERED, not silently converted to a login.
BeginMagicLinkSignIn(Email, OnSuccess, OnError)Opens a loopback listener on 127.0.0.1, emails the link, and completes itself when the player clicks it. OnError on failure or timeout. Nothing else to call.
RequestLoginLink(Email, RedirectUri, OnLinkSent, OnError), CompleteLoginLink(Token, OnSuccess, OnError)The two steps behind the one call above, for a flow you drive yourself. FOnLoginLinkSent carries one bSent, always true so an address cannot be probed.
GetAvailableLoginProviders(OnResult, OnError)The enabled providers (FOnLoginProvidersReceived, a string array). Public: no session needed. Build the sign-in buttons from it.
BeginSocialSignIn(Provider, OnSuccess, OnError)Opens the provider's consent page in the system browser, never an embedded view, and completes when the redirect lands on the loopback listener.
RestoreSession(OnSuccess, OnError)Restores a saved session and re-mints. Returns false at once, and calls OnError, when nothing is saved.
ClearSavedSession(), HasSavedSession()Delete the persisted credential and cancel the refresh timer; whether one exists on disk.
IsSignedIn()Signed in right now: a sign-in or restore completed and its app token is in memory.
RefreshAppToken()Rotate the app token by hand. It also happens automatically before expiry.
GetMyIdentities(OnResult, OnError), BeginLinkIdentity(Provider, OnResult, OnError), UnlinkIdentity(IdentityId, OnResult, OnError)Linked sign-in methods; below.

Events on UCrowdyAuthentication, all Blueprint-assignable: OnLogin (FOnAuthLoginEvent) and OnRegister (FOnAuthRegisterEvent) with an FCrowdyAuthResult; OnLoginFailed (FOnAuthLoginFailed) and OnRegisterFailed (FOnAuthRegisterFailed) with a message; OnSessionRestored (FOnAuthSessionRestored) and OnSessionRestoreFailed (FOnAuthSessionRestoreFailed); and OnAppTokenRefreshed (FOnAppTokenRefreshed), with no parameters, after a proactive or reactive rotation.

FCrowdyAuthResult has two fields: UserID, the player's int64 id, and GameToken, the session token you never read.

Signing in​

The Game Instance signs the player in from Init, binds the outcome event before it calls, and enables play in the handler. This is the Quickstart's step, on the same ULanternGameInstance.

The C++ is the ULanternGameInstance block on Player Sign-in: bind OnLogin and OnUDPConnectionSuccess on the Crowdy SDK Subsystem in Init, then call Login with the account read from Config/DefaultGame.ini. The rich surface in the table above is reached the same way, through GetSubsystem<UCrowdyAuthentication>(), with the per-call delegates passed as arguments.

Bind your delegates before you start the call.

A response can arrive quickly enough to be missed by a handler bound after the call returns. The per-call delegates are bound as arguments, so they are always in place; for the multicast events, bind in Init before the first sign-in.

In Blueprint, five latent nodes wrap the rich surface, each with On Success (a Result) and On Error (a Message) pins and no delegate wiring: Login (UCrowdyAuth_Login), Register (UCrowdyAuth_Register), Magic Link Sign In (UCrowdyAuth_BeginMagicLinkSignIn), Social Sign In (UCrowdyAuth_BeginSocialSignIn), and Restore Session (UCrowdyAuth_RestoreSession). Their pin delegate types are FLoginDelegateOnSuccess, FLoginDelegateOnError, FRegisterDelegateOnSuccess, FRegisterDelegateOnFailure, FBeginMagicLinkSignInDelegateOnSuccess, FBeginMagicLinkSignInDelegateOnError, FBeginSocialSignInDelegateOnSuccess, FBeginSocialSignInDelegateOnError, FRestoreSessionDelegateOnSuccess, and FRestoreSessionDelegateOnError, should you bind one from C++.

Register​

A Create Account action on your sign-in screen calls RegisterNewAccount; success is the account created and signed in. It binds the two handlers every sign-in path on this page shares, HandleSignedIn and HandleSignInFailed, with BindDynamic before the call.

// LanternGameInstance.h
#include "Subsystem/CrowdyAuthentication.h"

// A Create Account button calls this; the outcome arrives on the two handlers every sign-in path shares.
UFUNCTION(BlueprintCallable, Category = "Lantern")
void RegisterNewAccount(const FString& NewEmail, const FString& NewPassword);

// LanternGameInstance.cpp
void ULanternGameInstance::RegisterNewAccount(const FString& NewEmail, const FString& NewPassword)
{
UCrowdyAuthentication* Auth = GetSubsystem<UCrowdyAuthentication>();
FOnAuthSuccess OnSuccess;
OnSuccess.BindDynamic(this, &ULanternGameInstance::HandleSignedIn);
FOnAuthError OnError;
OnError.BindDynamic(this, &ULanternGameInstance::HandleSignInFailed);
Auth->Register(NewEmail, NewPassword, OnSuccess, OnError);
}

One call, RequestMagicLink. The SDK opens the loopback listener, sends the email, and completes the sign-in when the player follows the link. There is no second step for you to call, and OnError fires if the player never returns.

// LanternGameInstance.h
#include "Subsystem/CrowdyAuthentication.h"

UFUNCTION(BlueprintCallable, Category = "Lantern")
void RequestMagicLink(const FString& MagicEmail);

// LanternGameInstance.cpp
void ULanternGameInstance::RequestMagicLink(const FString& MagicEmail)
{
UCrowdyAuthentication* Auth = GetSubsystem<UCrowdyAuthentication>();
FOnAuthSuccess OnSuccess;
OnSuccess.BindDynamic(this, &ULanternGameInstance::HandleSignedIn);
FOnAuthError OnError;
OnError.BindDynamic(this, &ULanternGameInstance::HandleSignInFailed);
// Emails the link and listens on 127.0.0.1 for the click; nothing else to call, OnError also covers the timeout.
Auth->BeginMagicLinkSignIn(MagicEmail, OnSuccess, OnError);
}

Social​

SignInWithGoogle opens the provider's consent page in the system browser. The provider string comes from GetAvailableLoginProviders; the example hard-codes google only to stay short.

// LanternGameInstance.h
#include "Subsystem/CrowdyAuthentication.h"

UFUNCTION(BlueprintCallable, Category = "Lantern")
void SignInWithGoogle();

// LanternGameInstance.cpp
void ULanternGameInstance::SignInWithGoogle()
{
UCrowdyAuthentication* Auth = GetSubsystem<UCrowdyAuthentication>();
FOnAuthSuccess OnSuccess;
OnSuccess.BindDynamic(this, &ULanternGameInstance::HandleSignedIn);
FOnAuthError OnError;
OnError.BindDynamic(this, &ULanternGameInstance::HandleSignInFailed);
// Opens the provider's consent page in the system browser; build a real sign-in screen from GetAvailableLoginProviders.
Auth->BeginSocialSignIn(TEXT("google"), OnSuccess, OnError);
}
Build the provider list from Get Available Login Providers, never from a literal.

A server can disable or rename a provider; a hard-coded button then opens a consent page that fails. Do not hard-code a provider you saw once in the list; build the buttons from the call every launch.

After sign-in​

The SDK requests the realtime connection itself, on every path: password, link, social, and restore all end in the same success handler inside the SDK, which calls RequestUDPAccess(), and a token refresh reinstalls the new token on the live connection. Do not call RequestUDPAccess() from your own success handler; by the time it runs, the SDK already did. Wait for On UDP Connection Success on the Crowdy SDK Subsystem for the moment the connection is up; Connection and reconnect covers what follows, including TOKEN_EXPIRED and the automatic re-mint. RequestUDPAccess() remains callable for a manual recovery and nothing else.

The events are also where a game enables play. WatchAuthEvents binds OnLogin and OnSessionRestored to the same HandleSignedIn, which lights every lantern in the world, and the Result it receives is read for nothing: the token in it belongs to the SDK.

// LanternGameInstance.h
#include "Subsystem/CrowdyAuthentication.h"

void WatchAuthEvents();

UFUNCTION()
void HandleSignedIn(FCrowdyAuthResult Result);

UFUNCTION()
void HandleSignInFailed(FString Message);

// LanternGameInstance.cpp
#include "EngineUtils.h"
#include "Lantern.h"

void ULanternGameInstance::WatchAuthEvents()
{
UCrowdyAuthentication* Auth = GetSubsystem<UCrowdyAuthentication>();
Auth->OnLogin.AddDynamic(this, &ULanternGameInstance::HandleSignedIn);
Auth->OnSessionRestored.AddDynamic(this, &ULanternGameInstance::HandleSignedIn);
}

void ULanternGameInstance::HandleSignedIn(FCrowdyAuthResult Result)
{
// Result also carries the session token; the SDK holds it, so never print or store it. The village lights up.
for (TActorIterator<ALantern> It(GetWorld()); It; ++It)
{
It->Light->SetVisibility(true);
}
}

void ULanternGameInstance::HandleSignInFailed(FString Message)
{
UE_LOG(LogTemp, Warning, TEXT("Sign-in failed: %s"), *Message);
}

What you may read about the signed-in player​

UCrowdyGameSession (Game Instance subsystem, category Crowdy SDK, Game Session) is the client's own connection and sign-in state, written by the SDK's login and handshake. A game legitimately reads four things from it:

AccessorReturns
GetAppID()The app id, int64.
GetUserID()The signed-in user's id, int64.
GetUUID(), GetID()This client's actor identity as a string and as an FGuid.

Its two events, OnOwnerUUIDUpdated (FOnOwnerUUIDUpdated) and OnHostIDUpdated (FOnHostIDUpdated), announce a new client identity and a change of the elected view-plane host; the host one belongs to Host election. Everything else on the class, the setters, the token and URL getters, the clear call, and the getter that returns the whole connection-state struct, is written by the SDK and is not for a game to read, display, or log: that struct carries both tokens as fields, so breaking it into a widget is exactly the leak the box above forbids.

Session persistence and sign-out​

Login, Register, the link, and the social flow persist the session token; RestoreSession on the next launch re-mints from it and fires OnSessionRestored, and the SDK connects as after any sign-in. Gate your login screen on IsSignedIn().

Restore a saved session​

A returning player should type nothing. The Game Instance calls RestoreSession(OnSuccess, OnError) first, before it shows a sign-in screen or calls Login. The call returns bool: true means a saved session was found and the restore is under way, and the outcome arrives on the delegates; false means nothing is saved on this machine, OnError has already run with No saved session found, and OnSessionRestoreFailed has already fired, so the error path is the sign-in screen. On success the SDK re-mints the app token from the saved session token, calls OnSuccess, and fires OnSessionRestored; then, exactly as after a Login, it requests the realtime connection with the fresh app token and raises its own OnLogin on the Crowdy SDK Subsystem, so the handlers you bound for a first sign-in, OnLogin and OnUDPConnectionSuccess, run unchanged. A restore that fails after finding a saved session (the token was revoked, the network is down) reaches OnError the same way.

RestoreOrSignIn is what Init calls in place of the bare Login from the Quickstart. OnSuccess is the same HandleSignedIn every path on this page binds; HandleRestoreFailed falls through to Login with the configured account, and a game with a sign-in screen shows it there instead. The return value is ignored on purpose: both outcomes arrive on the delegates.

// LanternGameInstance.h
#include "Subsystem/CrowdyAuthentication.h"

// Init calls this in place of Login: a machine that signed in before comes back without typing anything.
void RestoreOrSignIn();

UFUNCTION()
void HandleRestoreFailed(FString Message);

// LanternGameInstance.cpp
void ULanternGameInstance::RestoreOrSignIn()
{
UCrowdyAuthentication* Auth = GetSubsystem<UCrowdyAuthentication>();
FOnAuthSuccess OnSuccess;
OnSuccess.BindDynamic(this, &ULanternGameInstance::HandleSignedIn);
FOnAuthError OnError;
OnError.BindDynamic(this, &ULanternGameInstance::HandleRestoreFailed);
// False means nothing is saved on this machine and OnError has already run; a saved token the server refuses reaches OnError later.
Auth->RestoreSession(OnSuccess, OnError);
}

void ULanternGameInstance::HandleRestoreFailed(FString Message)
{
GetSubsystem<UCrowdySDKSubsystem>()->Login(Email, Password);
}
IsSignedIn is not HasSavedSession.

HasSavedSession only says a credential exists on disk. It stays true across a restart, before a restore has run, and after a restore that failed. IsSignedIn is true only while a sign-in or restore has completed and its app token is held in memory right now.

Sign out with Logout on the Crowdy SDK Subsystem, not ClearSavedSession alone.

Logout() clears the saved session, clears the connection state, scrubs both bearers from the shared API client, and closes the realtime connection, then broadcasts OnLogout. ClearSavedSession() only deletes the on-disk credential; called alone during a live session it leaves the in-memory tokens and the open connection signed in as the player who just left.

Linked identities​

A signed-in player can attach more ways to sign in. GetMyIdentities lists them (FOnIdentitiesReceived, an array of FCrowdyUserIdentity: IdentityId, UserId, Provider, Subject, Email, bEmailVerified, CreatedAt, LastLoginAt). BeginLinkIdentity(Provider, ...) runs the same browser and loopback flow as the social sign-in but attaches the result to the current session instead of starting one (FOnIdentityLinked). UnlinkIdentity(IdentityId, ...) removes one (FOnIdentityUnlinked, bRemoved). All three need an active session.

The server refuses to unlink a player's last remaining sign-in method.

Expect the refusal in a UI that lists identities with a remove button; do not treat it as an error to retry.

Gotchas​

  • Nothing connects until someone signs in. Every entity and event depends on it.
  • Register on an existing address is a refusal, EMAIL_ALREADY_REGISTERED; offer Login instead.
  • The link and social flows need the player to reach a browser or inbox while the game runs; on a machine that cannot, use a password.
  • OnAppTokenRefreshed fires on every rotation. You rarely need it; the connection is re-armed for you.
  • Studio's own sign-in is the same account and the same methods; Signing In.