Skip to content

Instantly share code, notes, and snippets.

@gamefish
Last active April 18, 2022 07:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gamefish/7c1341de94bbcbfb2bbb9bcc5a03e966 to your computer and use it in GitHub Desktop.
Save gamefish/7c1341de94bbcbfb2bbb9bcc5a03e966 to your computer and use it in GitHub Desktop.
[Sign in with Apple Unity official example] Showcase how to sign in with Apple account in unity
public SignInWithApple signInWithApple;
signInWithApple.Login((args) =>
{
if(!string.IsNullOrEmpty(args.error))
{
LogMessage("Apple -> sign in error:" + args.error);
return;
}
string idToken = args.userInfo.idToken;
LogMessage("Apple --> Identity Token:\n"+idToken);
// use idToken to login to PlayFab or any other web services
});
using System;
using System.Collections.Concurrent;
using System.Runtime.InteropServices;
using AOT;
using UnityEngine.Events;
namespace UnityEngine.SignInWithApple
{
public enum UserDetectionStatus
{
LikelyReal,
Unknown,
Unsupported
}
public enum UserCredentialState
{
Revoked,
Authorized,
NotFound
}
public struct UserInfo
{
public string userId;
public string email;
public string displayName;
public string idToken;
public string error;
public UserDetectionStatus userDetectionStatus;
}
[System.Serializable]
public class SignInWithAppleEvent : UnityEvent<SignInWithApple.CallbackArgs>
{
}
public class SignInWithApple : MonoBehaviour
{
private static Callback s_LoginCompletedCallback;
private static Callback s_CredentialStateCallback;
private static readonly ConcurrentQueue<Action> s_EventQueue = new ConcurrentQueue<Action>();
public struct CallbackArgs
{
/// <summary>
/// The state of the user's authorization.
/// </summary>
public UserCredentialState credentialState;
/// <summary>
/// The logged in user info after the call is done.
/// </summary>
public UserInfo userInfo;
/// <summary>
/// Whether the call ends up with an error.
/// </summary>
public string error;
}
public delegate void Callback(CallbackArgs args);
private delegate void LoginCompleted(int result, UserInfo info);
[MonoPInvokeCallback(typeof(LoginCompleted))]
private static void LoginCompletedCallback(int result, [MarshalAs(UnmanagedType.Struct)]UserInfo info)
{
var args = new CallbackArgs();
if (result != 0)
{
args.userInfo = new UserInfo
{
idToken = info.idToken,
displayName = info.displayName,
email = info.email,
userId = info.userId,
userDetectionStatus = info.userDetectionStatus
};
}
else
{
args.error = info.error;
}
s_LoginCompletedCallback(args);
s_LoginCompletedCallback = null;
}
private delegate void GetCredentialStateCompleted(UserCredentialState state);
[MonoPInvokeCallback(typeof(GetCredentialStateCompleted))]
private static void GetCredentialStateCallback([MarshalAs(UnmanagedType.SysInt)]UserCredentialState state)
{
var args = new CallbackArgs
{
credentialState = state
};
s_CredentialStateCallback(args);
s_CredentialStateCallback = null;
}
#region events
[Header("Event fired when login is complete.")]
public SignInWithAppleEvent onLogin;
[Header("Event fired when the users credential state has been retrieved.")]
public SignInWithAppleEvent onCredentialState;
[Header("Event fired when there is an error.")]
public SignInWithAppleEvent onError;
#endregion
/// <summary>
/// Get credential state and trigger onCredentialState or onError event when action is completed.
/// </summary>
/// <param name="userID">The user id to query the credential state on.</param>
public void GetCredentialState(string userID)
{
GetCredentialState(userID, TriggerCredentialStateEvent);
}
/// <summary>
/// Invoke login and provide a custom callback when action is completed.
/// When a custom trigger is used, the onCredentialState or onError unity event won't trigger.
/// </summary>
/// <param name="userID">The user id to query the credential state on.</param>
/// <param name="callback">The custom callback to trigger when action is completed.</param>
public void GetCredentialState(string userID, Callback callback)
{
if (s_CredentialStateCallback != null)
throw new InvalidOperationException("Credential state fetch called while another request is in progress");
s_CredentialStateCallback = callback;
GetCredentialStateInternal(userID);
}
private void GetCredentialStateInternal(string userID)
{
#if (UNITY_IOS || UNITY_TVOS) && !UNITY_EDITOR
IntPtr cback = IntPtr.Zero;
GetCredentialStateCompleted d = GetCredentialStateCallback;
cback = Marshal.GetFunctionPointerForDelegate(d);
UnitySignInWithApple_GetCredentialState(userID, cback);
#endif
}
/// <summary>
/// Invoke login and trigger onLogin or onError event when login is completed.
/// </summary>
public void Login()
{
Login(TriggerOnLoginEvent);
}
/// <summary>
/// Invoke login and provide a custom callback when login is completed.
/// When a custom trigger is used, the onLogin or onError unity event won't trigger.
/// </summary>
/// <param name="callback">The custom callback to trigger when login is completed.</param>
public void Login(Callback callback)
{
Debug.Log("siwa Login");
if (s_LoginCompletedCallback != null)
throw new InvalidOperationException("Login called while another login is in progress");
s_LoginCompletedCallback = callback;
LoginInternal();
}
private void LoginInternal()
{
Debug.Log("siwa LoginInternal");
#if (UNITY_IOS || UNITY_TVOS) && !UNITY_EDITOR
IntPtr cback = IntPtr.Zero;
LoginCompleted d = LoginCompletedCallback;
cback = Marshal.GetFunctionPointerForDelegate(d);
UnitySignInWithApple_Login(cback);
#endif
}
private void TriggerOnLoginEvent(CallbackArgs args)
{
if (args.error != null)
{
TriggerOnErrorEvent(args);
return;
}
s_EventQueue.Enqueue(delegate ()
{
if (onLogin != null)
{
onLogin.Invoke(args);
}
});
}
private void TriggerCredentialStateEvent(CallbackArgs args)
{
if (args.error != null)
{
TriggerOnErrorEvent(args);
return;
}
s_EventQueue.Enqueue(delegate ()
{
if (onCredentialState != null)
{
onCredentialState.Invoke(args);
}
});
}
private void TriggerOnErrorEvent(CallbackArgs args)
{
s_EventQueue.Enqueue(delegate ()
{
if (onError != null)
{
onError.Invoke(args);
}
});
}
public void Update()
{
Action action;
while (s_EventQueue.TryDequeue(out action))
{
action.Invoke();
}
}
#region native hooks
#if (UNITY_IOS || UNITY_TVOS) && !UNITY_EDITOR
[DllImport("__Internal")]
private static extern void UnitySignInWithApple_Login(IntPtr callback);
[DllImport("__Internal")]
private static extern void UnitySignInWithApple_GetCredentialState(string userID, IntPtr callback);
#endif
#endregion
}
}
#import <Foundation/Foundation.h>
#import <AuthenticationServices/AuthenticationServices.h>
#import "UnityAppController.h"
struct UserInfo
{
const char * userId;
const char * email;
const char * displayName;
const char * idToken;
const char * error;
ASUserDetectionStatus userDetectionStatus;
};
typedef void (*SignInWithAppleCallback)(int result, struct UserInfo s1);
API_AVAILABLE(ios(13.0), tvos(13.0))
typedef void (*CredentialStateCallback)(ASAuthorizationAppleIDProviderCredentialState state);
API_AVAILABLE(ios(13.0), tvos(13.0))
@interface UnitySignInWithApple : NSObject<ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding>
@property (nonatomic) SignInWithAppleCallback loginCallback;
@property (nonatomic) CredentialStateCallback credentialStateCallback;
@end
API_AVAILABLE(ios(13.0), tvos(13.0))
static UnitySignInWithApple* _unitySignInWithAppleInstance;
@implementation UnitySignInWithApple
{
ASAuthorizationAppleIDRequest* request;
}
+(UnitySignInWithApple*)instance
{
if (_unitySignInWithAppleInstance == nil)
_unitySignInWithAppleInstance = [[UnitySignInWithApple alloc] init];
return _unitySignInWithAppleInstance;
}
-(void)startRequest
{
if (@available(iOS 13.0, tvOS 13.0, *)) {
ASAuthorizationAppleIDProvider* provider = [[ASAuthorizationAppleIDProvider alloc] init];
request = [provider createRequest];
[request setRequestedScopes: @[ASAuthorizationScopeEmail, ASAuthorizationScopeFullName]];
ASAuthorizationController* controller = [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[request]];
controller.delegate = self;
controller.presentationContextProvider = self;
[controller performRequests];
} else {
// Fallback on earlier versions
}
}
- (void)getCredentialState:(NSString *)userID
{
ASAuthorizationAppleIDProvider* provider = [[ASAuthorizationAppleIDProvider alloc] init];
[provider getCredentialStateForUserID:userID
completion:^(ASAuthorizationAppleIDProviderCredentialState credentialState, NSError * _Nullable error) {
self.credentialStateCallback(credentialState);
}];
}
-(ASPresentationAnchor)presentationAnchorForAuthorizationController:(ASAuthorizationController *)controller
{
return _UnityAppController.window;
}
-(void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization
{
if (self.loginCallback)
{
struct UserInfo data;
if (@available(iOS 13.0, tvOS 13.0, *)) {
ASAuthorizationAppleIDCredential* credential = (ASAuthorizationAppleIDCredential*)authorization.credential;
NSString* idToken = [[NSString alloc] initWithData:credential.identityToken encoding:NSUTF8StringEncoding];
NSPersonNameComponents* name = credential.fullName;
data.idToken = [idToken UTF8String];
data.displayName = [[NSPersonNameComponentsFormatter localizedStringFromPersonNameComponents:name
style:NSPersonNameComponentsFormatterStyleDefault
options:0] UTF8String];
data.email = [credential.email UTF8String];
data.userId = [credential.user UTF8String];
data.userDetectionStatus = credential.realUserStatus;
data.error = "";
self.loginCallback(1, data);
} else {
// Fallback on earlier versions
}
}
}
-(void)authorizationController:(ASAuthorizationController *)controller didCompleteWithError:(NSError *)error
{
if (self.loginCallback)
{
// All members need to be set to a non-null value.
struct UserInfo data;
data.idToken = "";
data.displayName = "";
data.email = "";
data.userId = "";
data.userDetectionStatus = 1;
data.error = [error.localizedDescription UTF8String];
self.loginCallback(0, data);
}
}
@end
void UnitySignInWithApple_Login(SignInWithAppleCallback callback)
{
if (@available(iOS 13.0, tvOS 13.0, *)) {
UnitySignInWithApple* login = [UnitySignInWithApple instance];
login.loginCallback = callback;
[login startRequest];
} else {
// Fallback on earlier versions
}
}
API_AVAILABLE(ios(13.0), tvos(13.0))
void UnitySignInWithApple_GetCredentialState(const char *userID, CredentialStateCallback callback)
{
if (@available(iOS 13.0, tvOS 13.0, *)) {
UnitySignInWithApple* login = [UnitySignInWithApple instance];
login.credentialStateCallback = callback;
[login getCredentialState: [NSString stringWithUTF8String: userID]];
} else {
// Fallback on earlier versions
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment