Skip to main content

Authentication

The sign-in buttons on the Login scene: Guest, username and password, Google and Apple. The SDK keeps the player signed in between plays; your Login UI only picks a method, shows progress and shows why a sign-in failed.

You useIt gives you
IProjectVAuthenticationPresenterObserve the sign-in state, sign in, register, sign out
ProjectVAuthenticationSnapshotWhat to draw: status, the signed-in player, the last error
ProjectVAuthenticationErrorWhy a command failed: Category, Code, RequestId, LocalizationKey
ProjectVAuthenticationCommandResultThe outcome of one command
IProjectVGuestIdentityProviderThis device's Guest ID
ProjectVNativeAuthenticationAvailabilityViewWhether the Google or Apple button can be shown
ProjectVPasswordRegistrationInputWhat the player typed into the register form

Authentication has no screen contract. After any successful sign-in, hand over to the Startup presenter, which loads the player's data and tells you when to open the game.

Quick start​

using System;
using System.Threading;
using Cysharp.Threading.Tasks;
using SilverTiger.ProjectV.Unity.Presentation.Authentication;
using SilverTiger.ProjectV.Unity.Presentation.Startup;
using UnityEngine;
using VContainer;

public sealed class LoginButtonsView : MonoBehaviour
{
private IProjectVAuthenticationPresenter authentication = null!;
private IProjectVGuestIdentityProvider guestIdentity = null!;
private IProjectVStartupPresenter startup = null!;
private IDisposable? subscription;
private CancellationTokenSource? lifetime;

[Inject]
public void Construct(
IProjectVAuthenticationPresenter authenticationPresenter,
IProjectVGuestIdentityProvider guestIdentityProvider,
IProjectVStartupPresenter startupPresenter)
{
authentication = authenticationPresenter;
guestIdentity = guestIdentityProvider;
startup = startupPresenter;
}

private async void OnEnable()
{
lifetime = new CancellationTokenSource();
var token = lifetime.Token;
var google = authentication.GetNativeAvailability(ProjectVNativeAuthenticationProvider.Google);
var apple = authentication.GetNativeAvailability(ProjectVNativeAuthenticationProvider.Apple);
ShowProviderButtons(google.IsAvailable, apple.IsAvailable);
var authenticationSub = await authentication.SubscribeAsync(OnAuthenticationChanged);
if (token.IsCancellationRequested)
{
// The page closed while subscribing.
authenticationSub.Dispose();
return;
}
subscription = authenticationSub;
}

private void OnDisable()
{
subscription?.Dispose();
subscription = null;
lifetime?.Cancel();
lifetime?.Dispose();
lifetime = null;
}

// May be called off the Unity main thread.
private void OnAuthenticationChanged(ProjectVAuthenticationSnapshot snapshot) => RenderAsync(snapshot).Forget();

private async UniTaskVoid RenderAsync(ProjectVAuthenticationSnapshot snapshot)
{
await UniTask.SwitchToMainThread();
if (this == null)
{
return;
}
SetButtonsInteractable(!snapshot.IsBusy);
ShowError(snapshot.Error?.LocalizationKey);
}

public async void OnGuestClicked()
{
var token = lifetime!.Token;
var id = await guestIdentity.GetOrCreateAsync(token);
var result = await authentication.AuthenticateDeviceAsync(id, null, token);
await ContinueAsync(result, token);
}

public async void OnGoogleClicked()
{
var token = lifetime!.Token;
var result = await authentication.SignInWithGoogleAsync(null, token);
await ContinueAsync(result, token);
}

private async UniTask ContinueAsync(ProjectVAuthenticationCommandResult result, CancellationToken token)
{
if (this == null || !result.Snapshot.IsAuthenticated)
{
return; // the listener already shows the error
}
// Startup loads the player's data; the startup listener opens the game on Ready.
await startup.StartAsync(contentReady: true, token);
}

private void ShowProviderButtons(bool google, bool apple) { }
private void SetButtonsInteractable(bool interactable) { }
private void ShowError(string? localizationKey) { }
}

The startup listener from the Startup quick start opens the game when it reports Ready. OperationCanceledException from a closing scene is left out here for brevity; catch it in real button handlers.

Main thread

SubscribeAsync listeners may run off the Unity main thread. Switch to the main thread before touching Unity objects, as RenderAsync does.

What to draw​

The listener receives a ProjectVAuthenticationSnapshot:

MemberTypeUse it for
StatusProjectVAuthenticationStatusSignedOut, Restoring, Authenticating, Authenticated, Failed
IsBusyboolRestoring or Authenticating: show a spinner and disable the sign-in buttons
IsAuthenticatedboolA player is signed in
UserId, Usernamestring?The signed-in player; null unless IsAuthenticated
ExpiresAtDateTimeOffset?When the current sign-in expires, for display only
ErrorProjectVAuthenticationError?Why the last command failed, or null. Can be set while SignedOut

authentication.Current holds the same snapshot; it starts as SignedOut.

Every command returns a ProjectVAuthenticationCommandResult with the Snapshot it ended on. IsSuccess means "no error"; it does not always mean signed in, so check Snapshot.IsAuthenticated.

Guest sign-in​

Task<string> IProjectVGuestIdentityProvider.GetOrCreateAsync(CancellationToken cancellationToken)
Task<ProjectVAuthenticationCommandResult> AuthenticateDeviceAsync(string id, ProjectVNativeAuthenticationOptions? options, CancellationToken cancellationToken)
ParameterTypeValue
idstringThe ID from GetOrCreateAsync, so the player keeps the same Guest account
optionsProjectVNativeAuthenticationOptions?null. Its Username and Create are not used yet
cancellationTokenCancellationTokenCancels the attempt, for example when the scene closes

GetOrCreateAsync creates a random ID once and keeps it on this device; call it on the Unity main thread. Uninstalling the game or clearing its data loses the ID, and with it the Guest account.

Returns Snapshot.IsAuthenticated true when the player is signed in; otherwise Snapshot.Error says why (see When it fails).

Username and password​

Task<ProjectVAuthenticationCommandResult> LoginPasswordAsync(string username, string password, CancellationToken cancellationToken)
Task<ProjectVAuthenticationCommandResult> RegisterPasswordAsync(ProjectVPasswordRegistrationInput input, CancellationToken cancellationToken)
ParameterTypeValue
username, passwordstringWhat the player typed. A blank username or empty password fails without contacting the server
inputProjectVPasswordRegistrationInputnew ProjectVPasswordRegistrationInput(username, password, email, displayName); email and displayName may be null

Returns

CommandIsSuccessSnapshotWhat to do
LoginPasswordAsynctrueAuthenticatedCall StartAsync(true, token) on Startup
RegisterPasswordAsynctrueSignedOutThe account was created, but the player is not signed in. Call LoginPasswordAsync with the same username and password
eitherfalseFailed with ErrorShow the error
var created = await authentication.RegisterPasswordAsync(
new ProjectVPasswordRegistrationInput(username, password, email: null, displayName: null),
token);
if (!created.IsSuccess)
{
ShowError(created.Snapshot.Error?.LocalizationKey);
return;
}

var signedIn = await authentication.LoginPasswordAsync(username, password, token);

Never show, store or log the password.

Google and Apple​

ProjectVNativeAuthenticationAvailabilityView GetNativeAvailability(ProjectVNativeAuthenticationProvider provider)
Task<ProjectVAuthenticationCommandResult> SignInWithGoogleAsync(ProjectVNativeAuthenticationOptions? options, CancellationToken cancellationToken)
Task<ProjectVAuthenticationCommandResult> SignInWithAppleAsync(ProjectVNativeAuthenticationOptions? options, CancellationToken cancellationToken)

Show a provider's button only when GetNativeAvailability(provider).IsAvailable is true. Decide from this value, never from the Unity platform alone:

AvailabilityMeaningButton
AvailableThe provider works hereShow
UnsupportedPlatformNot on this platform: Google outside Android, iOS and the Editor, Apple outside iOS and macOSHide
NotConfiguredThe build is missing its setup (Google: no configuration asset or Web client ID; in the Editor, also Editor sign-in turned off)Hide, and fix the project setup
ProviderUnavailableThe provider's plugin is missing or cannot be used right nowHide

Pass null for options. Each call opens the provider's own prompt, then signs the player in with the chosen account.

Google setup. Google sign-in is part of com.silvertiger.projectv. It needs a ProjectVGoogleOAuthConfiguration asset at Assets/Resources/GoogleOAuthConfig.asset (create it from Assets › Create › SilverTiger › ProjectV SDK › Google OAuth). Editor sign-in is off by default; enabling it needs a Web OAuth client with the redirect URI http://localhost:49153/projectv-google/callback. Check the setup with Tools › SilverTiger › ProjectV SDK › Google › Validate Configuration. Android builds must be signed with the keystore registered for the Android OAuth client.

Apple setup. Apple sign-in needs the Sign in with Apple Unity plugin (AppleAuth) in the project; without it the availability is ProviderUnavailable.

Restore and sign out​

Task<ProjectVAuthenticationCommandResult> RestoreAsync(CancellationToken cancellationToken)
Task<ProjectVAuthenticationCommandResult> LogoutAsync(CancellationToken cancellationToken)

Startup already signs the player back in, so the Login scene normally does not call RestoreAsync. When you do call it:

IsSuccessSnapshotMeaning
trueAuthenticatedThe saved sign-in worked
trueSignedOutThere was no saved sign-in. Show the sign-in options
falseSignedOut with ErrorThe saved sign-in was refused. Show the sign-in options
falseFailed with ErrorIt could not be checked, for example no connection

LogoutAsync signs the player out on this device and ends SignedOut. In game scenes use the Account presenter's LogoutAsync, which also tells you when to open Login.

When it fails​

Show the message for Error.LocalizationKey (projectv.authentication.<category>). Use Error.Code only to branch on one exact case:

SituationError.Category / CodeWhat the player sees
Blank username or empty passwordValidation, PROJECTV_AUTH_INVALID_CREDENTIALSAsk the player to fill in both fields; no server call was made
Blank register formValidation, PROJECTV_AUTH_INVALID_REGISTRATIONThe same, on the register form
Blank Guest IDValidation, PROJECTV_AUTH_PROVIDER_TOKEN_REQUIREDAn error; use GetOrCreateAsync
Wrong username or passwordAuthentication, the server's codeYour wrong-username-or-password message
Username already takenthe server's codeThe server's reason, on the register form
The player closed the Google or Apple promptCancelled, PROJECTV_AUTH_CANCELLEDNothing; leave the buttons enabled
Your token firedCancelled, PROJECTV_AUTH_CANCELLEDNothing; the scene is closing
Google or Apple not available hereUnavailable, PROJECTV_AUTH_PROVIDER_UNAVAILABLEHide the button (check availability first)
The provider prompt failedClient, PROJECTV_AUTH_PROVIDER_FAILEDAn error with the option to try again
No connection or a timeoutNetwork, Timeout or UnavailableA no-connection message; let the player try again
Anything unexpectedClient, PROJECTV_AUTH_BACKEND_FAILEDA generic error with the option to try again

Put Error.RequestId, when set, in a support panel.

Rules​

  • Commands run one at a time. A second tap while one runs waits for it and then runs too; it is not merged. Disable the buttons while IsBusy.
  • After any successful sign-in, call StartAsync(true, token) on Startup and open the game only on Ready.
  • Never store, log or show passwords, provider tokens or OAuth secrets.

Common mistakes​

  • Expecting registration to sign the player in. RegisterPasswordAsync ends SignedOut; call LoginPasswordAsync next.
  • Treating IsSuccess as signed in. Check Snapshot.IsAuthenticated.
  • Passing ProjectVNativeAuthenticationOptions to create an account. Its Username and Create are not used yet; pass null.
  • Showing the Google or Apple button by platform. Use GetNativeAvailability.
  • Making your own Guest ID. Use IProjectVGuestIdentityProvider, or the player gets a new Guest account.
  • Touching Unity objects in the listener without switching threads.

Scene gate and UI binding​

IProjectVAuthenticatedSceneGate remembers where to return after sign-in: a scene that needs a signed-in player checks IsReady, calls RequestReturnTo(sceneName) and opens Login; after sign-in, Login opens ConsumeReturnScene("Lobby"). You load the scenes; the gate only remembers the name.

authentication.SubscribeUiAsync(ProjectVUiBindingId.LoginAuthentication, apply, token) works like SubscribeAsync and also marks in the SDK log when apply returned (see UI subscriptions). It does not switch threads; switch to the main thread yourself.