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 use | It gives you |
|---|---|
IProjectVAuthenticationPresenter | Observe the sign-in state, sign in, register, sign out |
ProjectVAuthenticationSnapshot | What to draw: status, the signed-in player, the last error |
ProjectVAuthenticationError | Why a command failed: Category, Code, RequestId, LocalizationKey |
ProjectVAuthenticationCommandResult | The outcome of one command |
IProjectVGuestIdentityProvider | This device's Guest ID |
ProjectVNativeAuthenticationAvailabilityView | Whether the Google or Apple button can be shown |
ProjectVPasswordRegistrationInput | What 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.
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:
| Member | Type | Use it for |
|---|---|---|
Status | ProjectVAuthenticationStatus | SignedOut, Restoring, Authenticating, Authenticated, Failed |
IsBusy | bool | Restoring or Authenticating: show a spinner and disable the sign-in buttons |
IsAuthenticated | bool | A player is signed in |
UserId, Username | string? | The signed-in player; null unless IsAuthenticated |
ExpiresAt | DateTimeOffset? | When the current sign-in expires, for display only |
Error | ProjectVAuthenticationError? | 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)
| Parameter | Type | Value |
|---|---|---|
id | string | The ID from GetOrCreateAsync, so the player keeps the same Guest account |
options | ProjectVNativeAuthenticationOptions? | null. Its Username and Create are not used yet |
cancellationToken | CancellationToken | Cancels 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)
| Parameter | Type | Value |
|---|---|---|
username, password | string | What the player typed. A blank username or empty password fails without contacting the server |
input | ProjectVPasswordRegistrationInput | new ProjectVPasswordRegistrationInput(username, password, email, displayName); email and displayName may be null |
Returns
| Command | IsSuccess | Snapshot | What to do |
|---|---|---|---|
LoginPasswordAsync | true | Authenticated | Call StartAsync(true, token) on Startup |
RegisterPasswordAsync | true | SignedOut | The account was created, but the player is not signed in. Call LoginPasswordAsync with the same username and password |
| either | false | Failed with Error | Show 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:
Availability | Meaning | Button |
|---|---|---|
Available | The provider works here | Show |
UnsupportedPlatform | Not on this platform: Google outside Android, iOS and the Editor, Apple outside iOS and macOS | Hide |
NotConfigured | The 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 |
ProviderUnavailable | The provider's plugin is missing or cannot be used right now | Hide |
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:
IsSuccess | Snapshot | Meaning |
|---|---|---|
true | Authenticated | The saved sign-in worked |
true | SignedOut | There was no saved sign-in. Show the sign-in options |
false | SignedOut with Error | The saved sign-in was refused. Show the sign-in options |
false | Failed with Error | It 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:
| Situation | Error.Category / Code | What the player sees |
|---|---|---|
| Blank username or empty password | Validation, PROJECTV_AUTH_INVALID_CREDENTIALS | Ask the player to fill in both fields; no server call was made |
| Blank register form | Validation, PROJECTV_AUTH_INVALID_REGISTRATION | The same, on the register form |
| Blank Guest ID | Validation, PROJECTV_AUTH_PROVIDER_TOKEN_REQUIRED | An error; use GetOrCreateAsync |
| Wrong username or password | Authentication, the server's code | Your wrong-username-or-password message |
| Username already taken | the server's code | The server's reason, on the register form |
| The player closed the Google or Apple prompt | Cancelled, PROJECTV_AUTH_CANCELLED | Nothing; leave the buttons enabled |
| Your token fired | Cancelled, PROJECTV_AUTH_CANCELLED | Nothing; the scene is closing |
| Google or Apple not available here | Unavailable, PROJECTV_AUTH_PROVIDER_UNAVAILABLE | Hide the button (check availability first) |
| The provider prompt failed | Client, PROJECTV_AUTH_PROVIDER_FAILED | An error with the option to try again |
| No connection or a timeout | Network, Timeout or Unavailable | A no-connection message; let the player try again |
| Anything unexpected | Client, PROJECTV_AUTH_BACKEND_FAILED | A 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 onReady. - Never store, log or show passwords, provider tokens or OAuth secrets.
Common mistakes​
- Expecting registration to sign the player in.
RegisterPasswordAsyncendsSignedOut; callLoginPasswordAsyncnext. - Treating
IsSuccessas signed in. CheckSnapshot.IsAuthenticated. - Passing
ProjectVNativeAuthenticationOptionsto create an account. ItsUsernameandCreateare not used yet; passnull. - 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.