firebase / quickstart-unity

Firebase Quickstart Samples for Unity
https://firebase.google.com/games
Apache License 2.0
826 stars 428 forks source link

[FR] Make it easy to generate rawnonce and nonce for Sign in with Apple #1049

Open MuhsinEge opened 3 years ago

MuhsinEge commented 3 years ago

[REQUIRED] Please fill in the following fields:

[REQUIRED] Please describe the question here:

Hi there!

We are trying to provide multiple authentication options to our user such as Anonymous, Facebook, Google Play Game Services, Google Login, Game Center and Sign in with apple. I have worked around with almost all of them and succeed but Sign in with apple.

There are lack of resources about this topic and quickstart/firebase documents have not enough information. The question that i have is about the rawnonce.

I generate "rawnonce" as a random string which has 32 char length and i assume that i have to send it to firebase for authentication. There are also a nonce which is sha256 form of the rawnonce but i have no idea what to do with that. It says restore it on the app but even i restore it what if user deletes the app or clear the cache and what is the purpose of this string?

And if anyone of you have example of Siwa implementation it will be appreciated #145

Thankx.

DellaBitta commented 3 years ago

Hi @MuhsinEge,

For AppleSign the underlying Objective C implementation requires a nonce. See the ASAuthorizationOpenIDRequest. docs.

The raw nonce is the string which represents a random 32 character string. But the nonce parameter on the ASAuthorizationOpenIDRequest must be sha256 digest of that string value.

In this case:

The nonce gets encoded in the idToken that ASAuthorizationOpenIDRequest creates. Later on the backend, Firebase needs the rawNonce string in order to validate the SignInWithAppleRequest and it's nonce. Both values are required but at different stages of the authorization process.

I'm pretty certain there's no direct Unity API (yet?) but there are a few different ways to create an Apple Token with a nonce. I used Sign in with Apple in my testing, though I don't intend to endorse one over another.

The plugin doesn't return the rawNonce on its own so I had to edit it a bit. I added a const char * rawNonce to the UserInfo struct in UnitySignInWithApple.mm and plumbed the value all the way to the C# callback so I can pass the result to Firebase. And in UnitySignInWithApple.mm I generated a random 32 character string, saved it into my new UserInfo.rawNonce field before running stringBySha256HashingString to use it as an actual nonce in the ASAuthorizationOpenIDRequest.

In this way I generated the idToken via the iOS APIs, and I have a copy of the 32 character rawNonce string, too, so I can pass both to the Firebase Auth SDK.

I hope this helps.

MuhsinEge commented 3 years ago

@DellaBitta Hi, thanks for your information.

I am not familiar with managing .mm files and getting a C# call from them. I generate nonce & rawnonce directly on C#. But not sure if it will going to work properly. I can share the script that i code for sign in with apple if you want.

I read that lots of people do what you did but I have not enough experience on these topic to directly understand how it should be done. Is there a chance you can share more detailed implementation of it? I also use the same Sign in with apple plugin. If you are able to share the .mm file it would be great for me to take a look at it.

Thanks!

MuhsinEge commented 3 years ago

@DellaBitta I wanted to update you about my implementation. I generate a nonce in C# script and send it to the firebase. It worked just fine but i did not do anything with the rawnonce even I generated it. I'll test it in different situations and waiting for your response.

DellaBitta commented 3 years ago

Hi @MuhsinEge,

I can walk through the alterations I made to the Unity plugin. It's a bit old but I hope it helps, and please note that most of these changes are in the Unity plugin and aren't officially supported by the Firebase team.

The summary of work is as follows:

  1. Update the unity plugin so that it creates an AppleSignIn Request with a nonce field.
  2. Add code to the plugin to store a string version of this nonce.
  3. Translate the Objective C String into a C++ string and store it in a struct. The C++ Struct is passed into the C# envionment.
  4. Update the UnityPlugin C# code to translate the string from the C++ struct to the C# struct that it returns to your app.

Let's get started.

Your project should have some new directories which include Objective-C source (.m. files) and Unity C# files. These were creted when importing the Unity Technologies Sign In With Apple plugin. You'll have to edit both, but let's start with updating the Objective-C code to generate and retain the nonce.

Note: A lot of this nonce generation code is defined in the Firebase C++ SDK Sign In With. Apple Guide. I recommended reading the Handle the Sign-In flow section before proceeding as it provides some context for these changes:

In Unity Technologies/SignInWithApple/UnitySignInWithApple.m add a rawNonce field to the UserInfo struct, which is what will be passed back to C#.

struct UserInfo {
 ...
 const char * idToken;  // Preexists.
 const char * error;  // Preexists.
 const char * rawNonce;  // New.
 ...
};

Note: Throughout this post I've labeled code that should already be there as // Preexists. and code that you need to add as // New.

Add a rawNonce property to the UnitySignInWithApple Objective-C class:

...
@property (nonatomic) CredentialStateCallback credentialStateCallback;  // Preexists.
@property (nonatomic) NSString* rawNonce;  // New.
...

Add the -randomNonce method and the -stringBySha256HashingString method from the C++ guide. I put them just after the +(UnitySignInWithApple*)instance { ... } definition.

In startRequest, invoke the randomNonce method and save the result in the rawNonce property you just created.

if (@available(iOS 13.0, tvOS 13.0, *)) {  // Preexists.
  ASAuthorizationAppleIDProvider* provider = [[ASAuthorizationAppleIDProvider alloc] init];  // Preexists.
  self.rawNonce = [self randomNonce:32];   // New.
  ...

Still within startRequest, add a sha256 hash of rawNonce to the SignInWithApple request:

  ...
  [request setRequestedScopes: @[ASAuthorizationScopeEmail, ASAuthorizationScopeFullName]];   // Preexisting.
  request.nonce = [self stringBySha256HashingString:self.rawNonce];  // New.

The callback method -(void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization is invoked when the SignInRequest has been properly created by the iOS SDK. Here the results are translated into the C++ UserInfo struct - the same struct in which you added the const char * rawNonce field. Now we just need to alter didCompleteWithAuthorization so that it copies the ObjC value into the C++ struct:

  ...
  data.userDetectionStatus = credential.realUserStatus;  // Preexisting.
  data.error = "";  // Preexisting.
  data.rawNonce = [self.rawNonce UTF8String];  // New.
  ...

At this point the AppleSignInRequest idToken has the sha256 digest of our nonce in it, and we have an accessor to retrieve the rawNonce from Objective C code. Now to update the layer that bridges Objective C code to C# code.

In Assets/Unity Technologies/SignInWithApple/SignInWithApple.cs add public string rawNonce; to the public struct UserInfo which is the C# mirror of the C++ struct. And in private static void LoginCompletedCallback(...) copy the value from the C++ struct into the C# struct:

  ... 
  userId = info.userId,  // Preexisting.
  rawNonce = info.rawNonce,  // New.
  userDetectionStatus = info.userDetectionStatus // Preexisting.
  ...

Now in your Application, you have access to the raw nonce in the SignInWithApple OnAppleLogin callback.

private void OnAppleLogin(SignInWithApple.CallbackArgs args)
{
  idToken = args.userInfo.idToken; // Preexisting.
  rawNonce =  args.userInfo.rawNonce;   // New.
}

And with the idToken (which in part contains the sha256 hash of the nonce), and the rawNonce string, you now have the ingredients to create an OAuthProvider Credential and invoke the Firebase SignInWithCredentialAsync method:

 auth.SignInWithCredentialAsync(Firebase.Auth.OAuthProvider.GetCredential("apple.com",
        idToken, rawNonce, null)).ContinueWithOnMainThread(HandleSignInWithUser);

That should be it!

MuhsinEge commented 3 years ago

@DellaBitta Thanks a lot for all the info. I'll gonna try to do it tomorrow while I'm at work and provide feedback to you.

MuhsinEge commented 3 years ago

Hi @DellaBitta,

I have followed your guide;

There was an build error about the -stringBySha256HashingString and solved by #import <CommonCrypto/CommonHMAC.h>.

Now i can build but the code is stuck at the SignInWithApple.m. I'll add a image that shows the error. I'm trying to find a solution and i'll be glad if you have any information about it. Also i'll add my C# script and .m file for you to look at if its possible. Codes.zip

image

DellaBitta commented 3 years ago

Hi @MuhsinEge,

I'm not supposed to download .zip files due to an abundance of caution for security. Is there a way you could post the changes that you made to those Objective-C methods in this thread or maybe in something like Gist?

I'm not sure why that invocation would throw an error directly. It might be something in the startRequest method source that's causing the problem. Have you tried adding debugging logging in startRequest to see if it's actually invoked, and/or how far it gets?

Also, some questions:

Thanks!

MuhsinEge commented 3 years ago

Hi @DellaBitta

Here is a Gist .

I have only debugged the code on xCode with running the app on the iOS device, did not implement any special debugging but will do if its needed(Probably have to learn how to do it because i spend less time dealing with objective-C and xCode then android environment.).

for your questions;

Thanks, will be waiting for your respond.

DellaBitta commented 3 years ago

Hi @MuhsinEge,

Unfortunately nothing explictly jumps out to me as cause of your error. As you mention there's a new release of the UnitySignInForApple plugin since my testing took place. It looks like they added some concurrency protection which might be causing problems, but I'm not sure. I'm also not sure when I'll be able to test out this new version.

It might be worth using the version of the original release and adding your custom code incrementally. Start just by calling the default implementation from Unity to ensure it doesn't crash, then setting the nonce to the ASAuthorizationAppleIDRequest without anything to the UserInfo structure, etc. Call it again to see if it crashes or returns an idToken. Then plumb the rawNonce through the Objective-C -> Unity layer and see what happens.

I'm sorry that I don't have a more solid reply for you right now.

MuhsinEge commented 3 years ago

Hi @DellaBitta

Thanks for your answer. You have been very clear all this time. I realize that I was trying to implement it with current version not the original one. I'll going to be trying it with the original version and let you know if anything goes wrong.

Edit : I can not get the original version because asset store is only provide the latest version... This is a bad choice unity..

chkuang-g commented 3 years ago

This looks...pretty complicated. Let me change this to a FR. Also, Sign in with Apple is marked as deprecated (See the description), so it does not look like a solid solution.

@DellaBitta, Could you let @MuhsinEge know what is the intermediate workaround?

MuhsinEge commented 3 years ago

Hi @chkuang-g ,

Thanks for your reply and change it as a FR. I can not make it work with the latest Sign In With Apple version for unity. I don't know if its a solution but I generate the nonce and rawnonce in a C# script and it worked(I did not distribute this version of the app on store, tried it with device which i build our app directly from xCode.). But I'm not comfortable to distribute it like that.

MuhsinEge commented 3 years ago

@chkuang-g @DellaBitta Are there any news about the topic?

DellaBitta commented 3 years ago

Hi @MuhsinEge,

This remains in our backlog/roadmap but we don't have any updates for this yet. The tricky bit is this code is outside the scope of the Firebase Unity SDK itself in that it doesn't wrap a Firebase SDK but the Apple iOS SDKs for Apple Sign In. We would need to standup and provide long term support for a new plugin, and this adds overhead to the task.

If people continue to upvote this issue as blocker then it would help push-up its priority.