Skip to content

feat: add direct login support (#3819) #521

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
790 changes: 726 additions & 64 deletions sample/Assets/Scenes/Passport/UnauthenticatedScene.unity

Large diffs are not rendered by default.

53 changes: 48 additions & 5 deletions sample/Assets/Scripts/Passport/Login/LoginScript.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using Immutable.Passport;
using Immutable.Passport.Model;

public class LoginScript : MonoBehaviour
{
#pragma warning disable CS8618
[SerializeField] private Text Output;
[SerializeField] private Button DefaultLoginButton;
[SerializeField] private Button GoogleLoginButton;
[SerializeField] private Button AppleLoginButton;
[SerializeField] private Button FacebookLoginButton;
private Passport Passport;
#pragma warning restore CS8618

Expand All @@ -21,25 +26,61 @@ void Start()
{
ShowOutput("Passport Instance is null");
}

// Set up button listeners if buttons are assigned
if (DefaultLoginButton != null) DefaultLoginButton.onClick.AddListener(() => Login(DirectLoginMethod.None));
if (GoogleLoginButton != null) GoogleLoginButton.onClick.AddListener(() => Login(DirectLoginMethod.Google));
if (AppleLoginButton != null) AppleLoginButton.onClick.AddListener(() => Login(DirectLoginMethod.Apple));
if (FacebookLoginButton != null) FacebookLoginButton.onClick.AddListener(() => Login(DirectLoginMethod.Facebook));
}

/// <summary>
/// Logs into Passport using the selected auth method.
/// Logs into Passport using the default auth method.
/// </summary>
public async void Login()
{
await LoginAsync(DirectLoginMethod.None);
}

/// <summary>
/// Logs into Passport using the specified direct login method.
/// </summary>
/// <param name="directLoginMethod">The direct login method to use (Google, Apple, Facebook, or None for default)</param>
public async void Login(DirectLoginMethod directLoginMethod)
{
await LoginAsync(directLoginMethod);
}

/// <summary>
/// Internal async method that performs the actual login logic.
/// </summary>
/// <param name="directLoginMethod">The direct login method to use</param>
private async System.Threading.Tasks.Task LoginAsync(DirectLoginMethod directLoginMethod)
{
try
{
await Passport.Login();
SceneManager.LoadScene("AuthenticatedScene");
string methodName = directLoginMethod == DirectLoginMethod.None ? "default" : directLoginMethod.ToString();
ShowOutput($"Logging in with {methodName} method...");

bool success = await Passport.Login(useCachedSession: false, directLoginMethod: directLoginMethod);

if (success)
{
ShowOutput($"Successfully logged in with {methodName}");
SceneManager.LoadScene("AuthenticatedScene");
}
else
{
ShowOutput($"Failed to log in with {methodName}");
}
}
catch (OperationCanceledException ex)
{
ShowOutput($"Failed to login: cancelled {ex.Message}\\n{ex.StackTrace}");
ShowOutput($"Login cancelled: {ex.Message}");
}
catch (Exception ex)
{
ShowOutput($"Failed to login: {ex.Message}");
ShowOutput($"Login failed: {ex.Message}");
}
}

Expand All @@ -49,5 +90,7 @@ private void ShowOutput(string message)
{
Output.text = message;
}

Debug.Log($"[LoginScript] {message}");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;

namespace Immutable.Passport.Model
{
/// <summary>
/// Enum for direct login methods supported by Passport.
/// </summary>
[Serializable]
public enum DirectLoginMethod
{
None,
Google,
Apple,
Facebook
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System;

namespace Immutable.Passport.Model
{
/// <summary>
/// Request model for getting PKCE authentication URL.
/// </summary>
[Serializable]
internal class GetPKCEAuthUrlRequest
{
/// <summary>
/// Whether this is a connect to IMX operation (true) or just login (false).
/// </summary>
public bool isConnectImx;

/// <summary>
/// The direct login method to use for authentication.
/// </summary>
public string directLoginMethod;

/// <summary>
/// Creates a new GetPKCEAuthUrlRequest.
/// </summary>
/// <param name="isConnectImx">Whether this is a connect to IMX operation</param>
/// <param name="directLoginMethod">The direct login method to use</param>
public GetPKCEAuthUrlRequest(bool isConnectImx, DirectLoginMethod directLoginMethod)
{
this.isConnectImx = isConnectImx;
this.directLoginMethod = directLoginMethod == DirectLoginMethod.None ? null : directLoginMethod.ToString().ToLower();
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 7 additions & 3 deletions src/Packages/Passport/Runtime/Scripts/Private/PassportImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public class PassportImpl
private readonly PassportAnalytics _analytics = new();

private bool _pkceLoginOnly; // Used to differentiate between a login and connect
private DirectLoginMethod _directLoginMethod; // Store the direct login method for current operation
private UniTaskCompletionSource<bool>? _pkceCompletionSource;
private string _redirectUri;
private string _logoutRedirectUri;
Expand Down Expand Up @@ -97,7 +98,7 @@ public void SetCallTimeout(int ms)
_communicationsManager.SetCallTimeout(ms);
}

public UniTask<bool> Login(bool useCachedSession = false)
public UniTask<bool> Login(bool useCachedSession = false, DirectLoginMethod directLoginMethod = DirectLoginMethod.None)
{
if (useCachedSession)
{
Expand All @@ -112,6 +113,7 @@ public UniTask<bool> Login(bool useCachedSession = false)
var task = new UniTaskCompletionSource<bool>();
_pkceCompletionSource = task;
_pkceLoginOnly = true;
_directLoginMethod = directLoginMethod;
#if UNITY_STANDALONE_WIN || (UNITY_ANDROID && UNITY_EDITOR_WIN) || (UNITY_IPHONE && UNITY_EDITOR_WIN)
WindowsDeepLink.Initialise(_redirectUri, OnDeepLinkActivated);
#endif
Expand Down Expand Up @@ -161,7 +163,7 @@ private async UniTask<bool> Relogin()
return false;
}

public async UniTask<bool> ConnectImx(bool useCachedSession = false)
public async UniTask<bool> ConnectImx(bool useCachedSession = false, DirectLoginMethod directLoginMethod = DirectLoginMethod.None)
{
if (useCachedSession)
{
Expand All @@ -187,6 +189,7 @@ public async UniTask<bool> ConnectImx(bool useCachedSession = false)
UniTaskCompletionSource<bool> task = new UniTaskCompletionSource<bool>();
_pkceCompletionSource = task;
_pkceLoginOnly = false;
_directLoginMethod = directLoginMethod;

#if UNITY_STANDALONE_WIN || (UNITY_ANDROID && UNITY_EDITOR_WIN) || (UNITY_IPHONE && UNITY_EDITOR_WIN)
WindowsDeepLink.Initialise(_redirectUri, OnDeepLinkActivated);
Expand Down Expand Up @@ -272,7 +275,8 @@ private async UniTask LaunchAuthUrl()
{
try
{
string callResponse = await _communicationsManager.Call(PassportFunction.GET_PKCE_AUTH_URL);
var request = new GetPKCEAuthUrlRequest(!_pkceLoginOnly, _directLoginMethod);
string callResponse = await _communicationsManager.Call(PassportFunction.GET_PKCE_AUTH_URL, JsonUtility.ToJson(request));
StringResponse response = callResponse.OptDeserializeObject<StringResponse>();

if (response != null && response.success == true && response.result != null)
Expand Down
10 changes: 6 additions & 4 deletions src/Packages/Passport/Runtime/Scripts/Public/Passport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -288,23 +288,25 @@ public void SetCallTimeout(int ms)
/// Logs into Passport using Authorisation Code Flow with Proof Key for Code Exchange (PKCE).
/// This opens the user's default browser on desktop or an in-app browser on mobile.
/// <param name="useCachedSession">If true, Passport will attempt to re-authenticate the player using stored credentials. If re-authentication fails, it won't automatically prompt the user to log in again.</param>
/// <param name="directLoginMethod">Optional direct login method to use (google, apple, facebook). If None, the user will see the standard login page.
/// </summary>
/// <returns>
/// Returns true if login is successful, otherwise false.
/// </returns>
public async UniTask<bool> Login(bool useCachedSession = false)
public async UniTask<bool> Login(bool useCachedSession = false, DirectLoginMethod directLoginMethod = DirectLoginMethod.None)
{
return await GetPassportImpl().Login(useCachedSession);
return await GetPassportImpl().Login(useCachedSession, directLoginMethod);
}

/// <summary>
/// Logs the user into Passport using Authorisation Code Flow with Proof Key for Code Exchange (PKCE) and sets up the Immutable X provider.
/// This opens the user's default browser on desktop or an in-app browser on mobile.
/// <param name="useCachedSession">If true, Passport will attempt to re-authenticate the player using stored credentials. If re-authentication fails, it won't automatically prompt the user to log in again.</param>
/// <param name="directLoginMethod">Optional direct login method to use (google, apple, facebook). If None, the user will see the standard login page.
/// </summary>
public async UniTask<bool> ConnectImx(bool useCachedSession = false)
public async UniTask<bool> ConnectImx(bool useCachedSession = false, DirectLoginMethod directLoginMethod = DirectLoginMethod.None)
{
return await GetPassportImpl().ConnectImx(useCachedSession);
return await GetPassportImpl().ConnectImx(useCachedSession, directLoginMethod);
}

/// <summary>
Expand Down
Loading