Skip to content
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

feat(auth,android): Apple Oauth provider support for Android #4188

Merged
merged 1 commit into from
Sep 1, 2020

Conversation

spezzino
Copy link
Contributor

@spezzino spezzino commented Sep 1, 2020

Description

Add apple.com Oauth Provider support for Android.

Motivation
There are several libraries around that support Apple Sign In for Android. The provider for Apple was not enabled, making it imposible to sign in with Firebase and Apple id_token as it requires a different credential builder. See https://firebase.google.com/docs/auth/android/apple#java_9

Release Summary

AppleAuthProvider support for Android.

Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
    • Yes
  • My change supports the following platforms;
    • Android
    • iOS
  • My change includes tests;
    • e2e tests added or updated in packages/\*\*/e2e
    • jest tests added or updated in packages/\*\*/__tests__
  • I have updated TypeScript types that are affected by my change.
  • This is a breaking change;
    • Yes
    • No

Test Plan

Tested in conjunction with other Apple SIgn In library. I was able to sign in with Firebase using Apple id_token.


Think react-native-firebase is great? Please consider supporting the project with any of the below:

🔥

@CLAassistant
Copy link

CLAassistant commented Sep 1, 2020

CLA assistant check
All committers have signed the CLA.

@vercel
Copy link

vercel bot commented Sep 1, 2020

This pull request is being automatically deployed with Vercel (learn more).
To see the status of your deployment, click below or on the icon next to each commit.

🔍 Inspect: https://vercel.com/invertase/react-native-firebase/7erzh6h3v
✅ Preview: https://react-native-firebase-git-fork-spezzino-master.invertase.vercel.app

@spezzino spezzino changed the title Apple Sign in support in android feat(auth,android): Apple Oauth provider support for Android Sep 1, 2020
Copy link
Collaborator

@mikehardy mikehardy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very cool thanks for this @spezzino !

I just wrote a new workflow for github that auto-generates patch-package patches for PRs and it looks like it worked, you can immediately apply this by downloading the zip of patches from the workflow run here https://github.com/invertase/react-native-firebase/suites/1126535643/artifacts/16014476

Do you have a library that you are using successfully to do sign in with Apple via Android? I/we could add some documentation to help others - both here and in our companion repo https://github.com/invertase/react-native-apple-authentication/

@mikehardy mikehardy merged commit c6e77a8 into invertase:master Sep 1, 2020
@spezzino
Copy link
Contributor Author

spezzino commented Sep 2, 2020

@mikehardy thanks for the quick turnaround. I'll test the patch-package and report back.

For now, I'm using this library which is a combined fork of other two libraries (one manages the native webview that triggers the authentication, and the other provides RN integration), with added support to exchange the nonce during authentication phase.

Quick example below

Install the dependency: npm i --save git+https://github.com/spezzino/react-native-apple-authentication-android.git#semver:1.0.7

Autolink the native library: npx react-native run-android

import firebase from '@react-native-firebase/app';
import '@react-native-firebase/auth';
import * as Crypto from 'expo-crypto';
import AppleAuthenticationAndroid, {
  NOT_CONFIGURED_ERROR,
  SIGNIN_CANCELLED_ERROR,
  SIGNIN_FAILED_ERROR,
  ResponseType,
  Scope,
} from 'react-native-apple-authentication-android';

async function signInWithAppleOauth() {
  const randomString = "randomly generated uuid";

  // create a sha256 of randomString, this will be the nonce sent to apple during authentication
  const nonce = await Crypto.digestStringAsync(
    Crypto.CryptoDigestAlgorithm.SHA256,
    randomString
  );

  AppleAuthenticationAndroid.configure({
    clientId: "your client id",
    redirectUri: "redirect uri",
    scope: Scope.ALL, // See repository for other scopes
    responseType: ResponseType.ID_TOKEN, // See repository for other response types
    nonce // required to generate the correct credential expected by Firebase
  });

  try {
    const response = await AppleAuthenticationAndroid.signIn(); // trigger the authentication flow
    if (response) {
      const code = response.code;
      const identityToken = response.id_token;
      const appleUser = response.user;

      console.log(code);
      console.log(identityToken);
      console.log(appleUser); // Note, this will only be provided if the user is signing in to your app for the first time. Thanks Apple :)

      const credential = firebase.auth.AppleAuthProvider.credential(identityToken, randomString); // Important: send the randomString, not the hashed nonce
      const firebaseProfileData = await firebase
          .auth()
          .signInWithCredential(credential);
      console.log(firebaseProfileData); // Success!
    }
  } catch (error) {
    if (error && error.message) {
      switch (error.message) {
        case NOT_CONFIGURED_ERROR:
          console.log("AppleAuthenticationAndroid not configured yet.");
          break;
        case SIGNIN_FAILED_ERROR:
          console.log("Apple signin failed.");
          break;
        case SIGNIN_CANCELLED_ERROR:
          console.log("User cancelled apple signin.");
          break;

        default:
          console.error(error);
          break;
      }
    } else {
      console.error(error);
    }
  }
}

@spezzino
Copy link
Contributor Author

spezzino commented Sep 2, 2020

@mikehardy the patch works, thanks

@mikehardy
Copy link
Collaborator

@spezzino thanks for double-checking the patch output of the new patch-package-generator workflow here, I appreciate that. This was a really small change but in the future it should help with PR verification here on any PR

I'll do a release of this soon, also I'm going to connect this with the react-native-apple-authentication issue tracking a possible Android implementation, it would be nice if that one library handled both!

@SocialAnxty
Copy link

SocialAnxty commented Sep 6, 2020

@mikehardy thanks for the quick turnaround. I'll test the patch-package and report back.

For now, I'm using this library which is a combined fork of other two libraries (one manages the native webview that triggers the authentication, and the other provides RN integration), with added support to exchange the nonce during authentication phase.

Quick example below

Install the dependency: npm i --save git+https://github.com/spezzino/react-native-apple-authentication-android.git#semver:1.0.7

Autolink the native library: npx react-native run-android

import firebase from '@react-native-firebase/app';
import '@react-native-firebase/auth';
import * as Crypto from 'expo-crypto';
import AppleAuthenticationAndroid, {
  NOT_CONFIGURED_ERROR,
  SIGNIN_CANCELLED_ERROR,
  SIGNIN_FAILED_ERROR,
  ResponseType,
  Scope,
} from 'react-native-apple-authentication-android';

async function signInWithAppleOauth() {
  const randomString = "randomly generated uuid";

  // create a sha256 of randomString, this will be the nonce sent to apple during authentication
  const nonce = await Crypto.digestStringAsync(
    Crypto.CryptoDigestAlgorithm.SHA256,
    randomString
  );

  AppleAuthenticationAndroid.configure({
    clientId: "your client id",
    redirectUri: "redirect uri",
    scope: Scope.ALL, // See repository for other scopes
    responseType: ResponseType.ID_TOKEN, // See repository for other response types
    nonce // required to generate the correct credential expected by Firebase
  });

  try {
    const response = await AppleAuthenticationAndroid.signIn(); // trigger the authentication flow
    if (response) {
      const code = response.code;
      const identityToken = response.id_token;
      const appleUser = response.user;

      console.log(code);
      console.log(identityToken);
      console.log(appleUser); // Note, this will only be provided if the user is signing in to your app for the first time. Thanks Apple :)

      const credential = firebase.auth.AppleAuthProvider.credential(identityToken, randomString); // Important: send the randomString, not the hashed nonce
      const firebaseProfileData = await firebase
          .auth()
          .signInWithCredential(credential);
      console.log(firebaseProfileData); // Success!
    }
  } catch (error) {
    if (error && error.message) {
      switch (error.message) {
        case NOT_CONFIGURED_ERROR:
          console.log("AppleAuthenticationAndroid not configured yet.");
          break;
        case SIGNIN_FAILED_ERROR:
          console.log("Apple signin failed.");
          break;
        case SIGNIN_CANCELLED_ERROR:
          console.log("User cancelled apple signin.");
          break;

        default:
          console.error(error);
          break;
      }
    } else {
      console.error(error);
    }
  }
}

When I try this I get "[Error: [auth/invalid-credential] The supplied auth credential is malformed, has expired or is not currently supported.]" any idea what could cause that? I am not using the expo crypto I am using the crypto built into nodejs could it be the nonce?

  const nonce = await crypto
      .createHmac('sha256', randomString)
      .update(uuid())
      .digest('hex');

that is how I am generating my nonce in my noed.js backend, which should do the same thing as your example, but for some reason I get that error. I don't think its nonce related, but thats the only difference in our code, so maybe?

@mikehardy
Copy link
Collaborator

Just a guess. When I have seen that before it was the nonce. Either reusing an old nonce (they are one use only right?) or formatting

@SocialAnxty
Copy link

Just a guess. When I have seen that before it was the nonce. Either reusing an old nonce (they are one use only right?) or formatting

Hmm I wonder what it could be. My nonce is in hex format, is that correct? I am not reusing an old one for sure, I have it generating a new one everytime I double checked that.

@spezzino
Copy link
Contributor Author

spezzino commented Sep 6, 2020 via email

@mikehardy
Copy link
Collaborator

@SocialAnxty I don't have a lot to offer, but you might look at the initial efforts of integration with ios from December

It was quite the saga, but has some "hand-crafted nonce" examples in it

#2884

https://gist.github.com/mikehardy/83c9535d71cec4a8764bfda5a492c25f

@SocialAnxty
Copy link

SocialAnxty commented Sep 6, 2020

From your example, you are creating a nonce each time you hash it. You need to keep the original uuid (non hashed version) for Firebase, and send the hashed version to apple.

On Sun, 6 Sep 2020 at 15:15, SocialAnxty @.***> wrote: Just a guess. When I have seen that before it was the nonce. Either reusing an old nonce (they are one use only right?) or formatting Hmm I wonder what it could be. My nonce is in hex format, is that correct? I am not reusing an old one for sure, I have it generating a new one everytime I double checked that. — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub <#4188 (comment)>, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAPLRNZJCUS3YWUJ7BG43LTSEL5FLANCNFSM4QRQX5VQ . --

@spezzino I am doing that.

exports.appleLoginNonce = functions.https.onRequest((req, res) => {
  async function getNonce() {
    let randomString = uuid();
    const nonce = await crypto
      .createHmac('sha256', randomString)
      .update(uuid())
      .digest('hex');

    let data = {
      nonce: nonce,
      rs: randomString,
    };

    if (nonce) {
      res.status(200).send(data);
    }
  }
  getNonce();
});

Basically I am calling this firebase function (node.js backend basically) which is sending me back the original randomString and the nonce.

Then I am passing the original random string and nonce exactly like the example code.

"you are creating a nonce each time you hash it" its possible I am doing this and do not know, but I am confused how?

Is that happening here?

crypto
      .createHmac('sha256', randomString)
      .update(uuid())
      .digest('hex');

I have tried just about everything else that could be wrong so you guys are likely onto something with the nonce I just can't figure out whats wrong with the nonce

@spezzino
Copy link
Contributor Author

spezzino commented Sep 6, 2020 via email

@SocialAnxty
Copy link

You are using randomString as the secret rather than the data. You can even try to hash withoit a key. Replace const nonce = await crypto .createHmac('sha256', randomString) .update(uuid()) .digest('hex'); With const nonce = await crypto .createHmac('sha256', uuid()) .update(randomString) .digest('hex');

On Sun, 6 Sep 2020 at 15:37, SocialAnxty @.> wrote: From your example, you are creating a nonce each time you hash it. You need to keep the original uuid (non hashed version) for Firebase, and send the hashed version to apple. … <#m_506649838913615581_> On Sun, 6 Sep 2020 at 15:15, SocialAnxty @.> wrote: Just a guess. When I have seen that before it was the nonce. Either reusing an old nonce (they are one use only right?) or formatting Hmm I wonder what it could be. My nonce is in hex format, is that correct? I am not reusing an old one for sure, I have it generating a new one everytime I double checked that. — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub <#4188 (comment) <#4188 (comment)>>, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAPLRNZJCUS3YWUJ7BG43LTSEL5FLANCNFSM4QRQX5VQ . -- @spezzino https://github.com/spezzino I am doing that. exports.appleLoginNonce = functions.https.onRequest((req, res) => { async function getNonce() { let randomString = uuid(); const nonce = await crypto .createHmac('sha256', randomString) .update(uuid()) .digest('hex'); let data = { nonce: nonce, rs: randomString, }; if (nonce) { res.status(200).send(data); } } getNonce(); }); Basically I am calling this firebase function (node.js backend basically) which is sending me back the original randomString and the nonce. Then I am passing the random string and nonce exactly like the example code. — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub <#4188 (comment)>, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAPLRN7D5QUS3R4ZSBYUNDTSEL7YTANCNFSM4QRQX5VQ . --

Weird even with that, same error. Maybe its not the nonce? Idk what else it could be, I have triple checked everything else =\

androidIsForVivek pushed a commit to androidIsForVivek/react-native-firebase that referenced this pull request Sep 15, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants