Last active
February 24, 2021 11:30
-
-
Save mikehardy/83c9535d71cec4a8764bfda5a492c25f to your computer and use it in GitHub Desktop.
react-native-firebase + apple sign-in (via expo-apple-authentication
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
jest.mock('@unimodules/core', () => { | |
return { | |
EventEmitter: jest.fn().mockImplementation(() => { | |
return {}; | |
}), | |
NativeModulesProxy: jest.fn(), | |
requireNativeViewManager: jest.fn(), | |
}; | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* This is pretty unstructured typescript but gives the idea */ | |
import * as AppleAuthentication from 'expo-apple-authentication'; | |
import { sha256 } from 'react-native-sha256'; | |
private _appleSignIn = async () => { | |
this.setState({ isSigninInProgress: true }); | |
try { | |
let linkSuccess = await UserStore.appleSignIn(true); | |
// signed in | |
console.log('PopupTest::_appleSignIn - result is ' + linkSuccess); | |
} catch (e) { | |
console.log('PopupTest::_appleSignIn - some error?', JSON.stringify(e, null, 2)); | |
} finally { | |
this.setState({ isSigninInProgress: false }); | |
} | |
}; | |
async componentDidMount() { | |
let appleSignInAvailable = await AppleAuthentication.isAvailableAsync(); | |
this.setState({ | |
appleSignInAvailable: appleSignInAvailable, | |
}); | |
} | |
render() { | |
return ( | |
{this.state.appleSignInAvailable && ( | |
<AppleAuthentication.AppleAuthenticationButton | |
buttonType={AppleAuthentication.AppleAuthenticationButtonType.SIGN_IN} | |
buttonStyle={AppleAuthentication.AppleAuthenticationButtonStyle.BLACK} | |
cornerRadius={5} | |
style={{ width: 200, height: 44 }} | |
onPress={this._appleSignIn} | |
/> | |
)} | |
); | |
} | |
getRandomString(length: number): string { | |
var result = ''; | |
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; | |
var charactersLength = characters.length; | |
for (var i = 0; i < length; i++) { | |
result += characters.charAt(Math.floor(Math.random() * charactersLength)); | |
} | |
return result; | |
} | |
async appleSignIn(link?: boolean): Promise<boolean> { | |
const nonce = this.getRandomString(32); | |
let nonceSHA256 = ''; | |
try { | |
nonceSHA256 = await sha256(nonce); | |
} catch (e) { | |
console.log('UserStore::appleSignin - unable to get nonceSHA256', e); | |
} | |
console.log('UserTest::appleSignIn - nonce is: ' + nonce); | |
console.log('UserTest::appleSignIn nonceSHA256 ' + nonceSHA256); | |
try { | |
const appleCredential = await AppleAuthentication.signInAsync({ | |
requestedScopes: [ | |
AppleAuthentication.AppleAuthenticationScope.FULL_NAME, | |
AppleAuthentication.AppleAuthenticationScope.EMAIL, | |
], | |
nonce: nonceSHA256, | |
}); | |
console.log( | |
'UserStore::appleSignIn - user result is:', | |
JSON.stringify(appleCredential, null, 2) | |
); | |
this.removeUserChangeListener(); | |
if (appleCredential.email) { | |
const providers = await this.getProvidersForEmail(appleCredential.email); | |
console.log( | |
'UserStore::appleSignIn - got providers for email: ', | |
JSON.stringify(providers, null, 2) | |
); | |
// if google.com is not in the providers, it is first login via google. | |
let appleProvider = false; | |
let passwordProvider = false; | |
for (let i = 0; i < providers.length; i++) { | |
if (providers[i] === 'apple.com') { | |
appleProvider = true; | |
} else if (providers[i] === 'password') { | |
passwordProvider = true; | |
} | |
} | |
// If there are providers, but not apple, google actually just automatically does it right. | |
if (providers.length !== 0 && !appleProvider) { | |
if (passwordProvider) { | |
console.log( | |
'UserStore::appleSignIn - other providers but google auto-connects if it is password provider' | |
); | |
} else { | |
this.handleCredentialInUse(); | |
return Promise.resolve(false); | |
} | |
} | |
// If there are no other providers we should ask if they already have an account | |
if ( | |
providers.length === 0 && | |
(!firebase.auth().currentUser?.providerData || | |
firebase.auth().currentUser?.providerData.length === 0) | |
) { | |
console.log('UserStore::appleSignIn - no other providers. See if they are sure'); | |
const choice = await AlertAsync( | |
I18NService.translate('LoginFirstLoginTitle'), | |
I18NService.translate('LoginFirstLoginText'), | |
[ | |
{ | |
text: I18NService.translate('LoginFirstLoginOtherAccountsButton'), | |
onPress: () => 'Other Accounts', | |
}, | |
{ | |
text: I18NService.translate('LoginFirstLoginPleaseContinueButton'), | |
onPress: () => 'Please Continue', | |
}, | |
], | |
{ | |
cancelable: false, | |
onDismiss: () => 'Please Continue', | |
} | |
); | |
if (choice === 'Other Accounts') { | |
console.log('UserStore::appleSignIn - user wants to handle other accounts'); | |
return Promise.resolve(false); | |
} | |
console.log( | |
'UserStore::appleSignIn - user is just fine continuing and creating new account' | |
); | |
this.setIsNewUser(true); | |
} | |
} else { | |
console.log('UserStore::appleSignIn - no email present, not first apple sign in?'); | |
} | |
// create a new firebase credential with the token | |
const credential = firebase.auth.AppleAuthProvider.credential( | |
appleCredential.identityToken, | |
nonce | |
); | |
console.log('UserStore::appleSignIn - credential is', JSON.stringify(credential, null, 2)); | |
let firebaseUserCredential; | |
if (!link) { | |
// login with credential | |
firebaseUserCredential = await firebase.auth().signInWithCredential(credential); | |
Analytics.setAnalyticsUser(firebaseUserCredential.user.uid); // TODO, set our own ID? | |
Analytics.setAnalyticsUserProperties({ email: firebaseUserCredential.user.email }); | |
Analytics.analyticsEvent('successAppleSignIn'); | |
} else { | |
if (!firebase.auth().currentUser) { | |
return Promise.resolve(false); | |
} | |
firebaseUserCredential = await firebase.auth().currentUser?.linkWithCredential(credential); | |
Analytics.setAnalyticsUser(firebase.auth().currentUser!.uid); // TODO, set our own ID? | |
Analytics.setAnalyticsUserProperties({ email: firebase.auth().currentUser!.email }); | |
Analytics.analyticsEvent('successAppleLink'); | |
} | |
console.log( | |
'UserStore::appleSignIn - firebaseCredential was', | |
JSON.stringify(firebaseUserCredential) | |
); | |
this.userChangedHandler( | |
firebaseUserCredential!.user, | |
firebaseUserCredential!.additionalUserInfo | |
); | |
return Promise.resolve(true); | |
} catch (error) { | |
if (error.code === 'ERR_CANCELLED') { | |
// user cancelled the login flow | |
console.log('UserStore::appleSignIn - user cancelled'); | |
} else if ( | |
error.code === 'auth/email-already-in-use' || | |
error.code === 'auth/credential-already-in-use' | |
) { | |
console.log('UserStore::appleSignIn - email already in use, instruct on unlink/delete'); | |
this.handleAccountInUse(); | |
} else if (error.code === 'auth/account-exists-with-different-credential') { | |
this.handleCredentialInUse(); | |
} else { | |
// some other error happened | |
console.log('UserStore::appleSignIn - unknown error?', JSON.stringify(error, null, 2)); | |
RX.Alert.show( | |
I18NService.translate('ConnectedAccountsConnectionError'), | |
I18NService.translate(error.code) | |
); | |
} | |
} finally { | |
this.addUserChangedListener(); | |
} | |
return Promise.resolve(false); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/node_modules/expo-apple-authentication/build/AppleAuthentication.js b/node_modules/expo-apple-authentication/build/AppleAuthentication.js | |
index bb30dc8..38bc6bf 100644 | |
--- a/node_modules/expo-apple-authentication/build/AppleAuthentication.js | |
+++ b/node_modules/expo-apple-authentication/build/AppleAuthentication.js | |
@@ -51,8 +51,4 @@ export async function getCredentialStateAsync(user) { | |
} | |
return ExpoAppleAuthentication.getCredentialStateAsync(user); | |
} | |
-const ExpoAppleAuthenticationEventEmitter = new EventEmitter(ExpoAppleAuthentication); | |
-export function addRevokeListener(listener) { | |
- return ExpoAppleAuthenticationEventEmitter.addListener('Expo.appleIdCredentialRevoked', listener); | |
-} | |
//# sourceMappingURL=AppleAuthentication.js.map | |
diff --git a/node_modules/expo-apple-authentication/build/AppleAuthentication.types.d.ts b/node_modules/expo-apple-authentication/build/AppleAuthentication.types.d.ts | |
index 50d827f..df8cbf7 100644 | |
--- a/node_modules/expo-apple-authentication/build/AppleAuthentication.types.d.ts | |
+++ b/node_modules/expo-apple-authentication/build/AppleAuthentication.types.d.ts | |
@@ -27,6 +27,11 @@ export declare type AppleAuthenticationSignInOptions = { | |
* avoid replay attacks. | |
*/ | |
state?: string; | |
+ | |
+ /** | |
+ * Data that is used to verify the uniqueness of a response and prevent replay attacks. | |
+ */ | |
+ nonce?: string; | |
}; | |
/** | |
* The options you can supply when making a call to `AppleAuthentication.refreshAsync()`. You must | |
diff --git a/node_modules/expo-apple-authentication/build/AppleAuthentication.types.js.map b/node_modules/expo-apple-authentication/build/AppleAuthentication.types.js.map | |
index b39bbca..51258db 100644 | |
--- a/node_modules/expo-apple-authentication/build/AppleAuthentication.types.js.map | |
+++ b/node_modules/expo-apple-authentication/build/AppleAuthentication.types.js.map | |
@@ -1 +1,2 @@ | |
-{"version":3,"file":"AppleAuthentication.types.js","sourceRoot":"","sources":["../src/AppleAuthentication.types.ts"],"names":[],"mappings":"AAqJA;;;;;;;;;;GAUG;AACH,MAAM,CAAN,IAAY,wBAGX;AAHD,WAAY,wBAAwB;IAClC,iFAAa,CAAA;IACb,yEAAS,CAAA;AACX,CAAC,EAHW,wBAAwB,KAAxB,wBAAwB,QAGnC;AAED,MAAM,CAAN,IAAY,4BAQX;AARD,WAAY,4BAA4B;IACtC;;OAEG;IACH,uFAAY,CAAA;IACZ,iFAAS,CAAA;IACT,qFAAW,CAAA;IACX,mFAAU,CAAA;AACZ,CAAC,EARW,4BAA4B,KAA5B,4BAA4B,QAQvC;AAED;;;;;;GAMG;AACH,MAAM,CAAN,IAAY,kCAKX;AALD,WAAY,kCAAkC;IAC5C,iGAAW,CAAA;IACX,uGAAc,CAAA;IACd,qGAAa,CAAA;IACb,yGAAe,CAAA;AACjB,CAAC,EALW,kCAAkC,KAAlC,kCAAkC,QAK7C;AAED;;;;;;;;GAQG;AACH,MAAM,CAAN,IAAY,sCAIX;AAJD,WAAY,sCAAsC;IAChD,iHAAe,CAAA;IACf,yGAAW,CAAA;IACX,iHAAe,CAAA;AACjB,CAAC,EAJW,sCAAsC,KAAtC,sCAAsC,QAIjD;AAED;;GAEG;AACH,MAAM,CAAN,IAAY,6BAGX;AAHD,WAAY,6BAA6B;IACvC,uFAAW,CAAA;IACX,yFAAY,CAAA;AACd,CAAC,EAHW,6BAA6B,KAA7B,6BAA6B,QAGxC;AAED;;GAEG;AACH,MAAM,CAAN,IAAY,8BAIX;AAJD,WAAY,8BAA8B;IACxC,qFAAS,CAAA;IACT,qGAAiB,CAAA;IACjB,qFAAS,CAAA;AACX,CAAC,EAJW,8BAA8B,KAA9B,8BAA8B,QAIzC","sourcesContent":["import { StyleProp, ViewStyle } from 'react-native';\n\nexport type AppleAuthenticationButtonProps = {\n onPress: () => void;\n buttonType: AppleAuthenticationButtonType;\n buttonStyle: AppleAuthenticationButtonStyle;\n cornerRadius?: number;\n style?: StyleProp<ViewStyle>;\n};\n\n/**\n * The options you can supply when making a call to `AppleAuthentication.signInAsync()`. None of\n * these options are required.\n *\n * @see [Apple\n * Documentation](https://developer.apple.com/documentation/authenticationservices/asauthorizationopenidrequest)\n * for more details.\n */\nexport type AppleAuthenticationSignInOptions = {\n /**\n * The scope of personal information to which your app is requesting access. The user can choose\n * to deny your app access to any scope at the time of logging in.\n * @defaults `[]` (no scopes).\n */\n requestedScopes?: AppleAuthenticationScope[];\n\n /**\n * Data that's returned to you unmodified in the corresponding credential after a successful\n * authentication. Used to verify that the response was from the request you made. Can be used to\n * avoid replay attacks.\n */\n state?: string;\n};\n\n/**\n * The options you can supply when making a call to `AppleAuthentication.refreshAsync()`. You must\n * include the ID string of the user whose credentials you'd like to refresh.\n *\n * @see [Apple\n * Documentation](https://developer.apple.com/documentation/authenticationservices/asauthorizationopenidrequest)\n * for more details.\n */\nexport type AppleAuthenticationRefreshOptions = {\n user: string;\n\n /**\n * The scope of personal information to which your app is requesting access. The user can choose\n * to deny your app access to any scope at the time of refreshing.\n * @defaults `[]` (no scopes).\n */\n requestedScopes?: AppleAuthenticationScope[];\n\n /**\n * Data that's returned to you unmodified in the corresponding credential after a successful\n * authentication. Used to verify that the response was from the request you made. Can be used to\n * avoid replay attacks.\n */\n state?: string;\n};\n\n/**\n * The options you can supply when making a call to `AppleAuthentication.signOutAsync()`. You must\n * include the ID string of the user to sign out.\n *\n * @see [Apple\n * Documentation](https://developer.apple.com/documentation/authenticationservices/asauthorizationopenidrequest)\n * for more details.\n */\nexport type AppleAuthenticationSignOutOptions = {\n user: string;\n\n /**\n * Data that's returned to you unmodified in the corresponding credential after a successful\n * authentication. Used to verify that the response was from the request you made. Can be used to\n * avoid replay attacks.\n */\n state?: string;\n};\n\n/**\n * The user credentials returned from a successful call to `AppleAuthentication.signInAsync()`,\n * `AppleAuthentication.refreshAsync()`, or `AppleAuthentication.signOutAsync()`.\n *\n * @see [Apple\n * Documentation](https://developer.apple.com/documentation/authenticationservices/asauthorizationappleidcredential)\n * for more details.\n */\nexport type AppleAuthenticationCredential = {\n /**\n * An identifier associated with the authenticated user. You can use this to check if the user is\n * still authenticated later. This is stable and can be shared across apps released under the same\n * development team. The same user will have a different identifier for apps released by other\n * developers.\n */\n user: string;\n\n /**\n * An arbitrary string that your app provided as `state` in the request that generated the\n * credential. Used to verify that the response was from the request you made. Can be used to\n * avoid replay attacks.\n */\n state: string | null;\n\n /**\n * The user's name. May be `null` or contain `null` values if you didn't request the `FULL_NAME`\n * scope, if the user denied access, or if this is not the first time the user has signed into\n * your app.\n */\n fullName: AppleAuthenticationFullName | null;\n\n /**\n * The user's email address. Might not be present if you didn't request the `EMAIL` scope. May\n * also be null if this is not the first time the user has signed into your app. If the user chose\n * to withhold their email address, this field will instead contain an obscured email address with\n * an Apple domain.\n */\n email: string | null;\n\n /**\n * A value that indicates whether the user appears to the system to be a real person.\n */\n realUserStatus: AppleAuthenticationUserDetectionStatus;\n\n /**\n * A JSON Web Token (JWT) that securely communicates information about the user to your app.\n */\n identityToken: string | null;\n\n /**\n * A short-lived session token used by your app for proof of authorization when interacting with\n * the app's server counterpart. Unlike `user`, this is ephemeral and will change each session.\n */\n authorizationCode: string | null;\n};\n\n/**\n * An object representing the tokenized portions of the user's full name.\n */\nexport type AppleAuthenticationFullName = {\n namePrefix: string | null;\n givenName: string | null;\n middleName: string | null;\n familyName: string | null;\n nameSuffix: string | null;\n nickname: string | null;\n};\n\nexport type AppleAuthenticationRevokeListener = () => void;\n\n/**\n * Scopes you can request when calling `AppleAuthentication.signInAsync()` or\n * `AppleAuthentication.refreshAsync()`.\n *\n * @note Note that it is possible that you will not be granted all of the scopes which you request.\n * You will still need to handle null values for any fields you request.\n *\n * @see [Apple\n * Documentation](https://developer.apple.com/documentation/authenticationservices/asauthorizationscope)\n * for more details.\n */\nexport enum AppleAuthenticationScope {\n FULL_NAME = 0,\n EMAIL = 1,\n}\n\nexport enum AppleAuthenticationOperation {\n /**\n * An operation that depends on the particular kind of credential provider.\n */\n IMPLICIT = 0,\n LOGIN = 1,\n REFRESH = 2,\n LOGOUT = 3,\n}\n\n/**\n * The state of the credential when checked with `AppleAuthentication.getCredentialStateAsync()`.\n *\n * @see [Apple\n * Documentation](https://developer.apple.com/documentation/authenticationservices/asauthorizationappleidprovidercredentialstate)\n * for more details.\n */\nexport enum AppleAuthenticationCredentialState {\n REVOKED = 0,\n AUTHORIZED = 1,\n NOT_FOUND = 2,\n TRANSFERRED = 3,\n}\n\n/**\n * A value that indicates whether the user appears to be a real person. You get this in the\n * realUserStatus property of a `Credential` object. It can be used as one metric to help prevent\n * fraud.\n *\n * @see [Apple\n * Documentation](https://developer.apple.com/documentation/authenticationservices/asuserdetectionstatus)\n * for more details.\n */\nexport enum AppleAuthenticationUserDetectionStatus {\n UNSUPPORTED = 0,\n UNKNOWN = 1,\n LIKELY_REAL = 2,\n}\n\n/**\n * Controls the predefined text shown on the authentication button.\n */\nexport enum AppleAuthenticationButtonType {\n SIGN_IN = 0,\n CONTINUE = 1,\n}\n\n/**\n * Controls the predefined style of the authenticating button.\n */\nexport enum AppleAuthenticationButtonStyle {\n WHITE = 0,\n WHITE_OUTLINE = 1,\n BLACK = 2,\n}\n"]} | |
\ No newline at end of file | |
+{"version":3,"file":"AppleAuthentication.types.js","sourceRoot":"","sources":["../src/AppleAuthentication.types.ts"],"names":[],"mappings":"AA0JA;;;;;;;;;;GAUG;AACH,MAAM,CAAN,IAAY,wBAGX;AAHD,WAAY,wBAAwB;IAClC,iFAAa,CAAA;IACb,yEAAS,CAAA;AACX,CAAC,EAHW,wBAAwB,KAAxB,wBAAwB,QAGnC;AAED,MAAM,CAAN,IAAY,4BAQX;AARD,WAAY,4BAA4B;IACtC;;OAEG;IACH,uFAAY,CAAA;IACZ,iFAAS,CAAA;IACT,qFAAW,CAAA;IACX,mFAAU,CAAA;AACZ,CAAC,EARW,4BAA4B,KAA5B,4BAA4B,QAQvC;AAED;;;;;;GAMG;AACH,MAAM,CAAN,IAAY,kCAKX;AALD,WAAY,kCAAkC;IAC5C,iGAAW,CAAA;IACX,uGAAc,CAAA;IACd,qGAAa,CAAA;IACb,yGAAe,CAAA;AACjB,CAAC,EALW,kCAAkC,KAAlC,kCAAkC,QAK7C;AAED;;;;;;;;GAQG;AACH,MAAM,CAAN,IAAY,sCAIX;AAJD,WAAY,sCAAsC;IAChD,iHAAe,CAAA;IACf,yGAAW,CAAA;IACX,iHAAe,CAAA;AACjB,CAAC,EAJW,sCAAsC,KAAtC,sCAAsC,QAIjD;AAED;;GAEG;AACH,MAAM,CAAN,IAAY,6BAGX;AAHD,WAAY,6BAA6B;IACvC,uFAAW,CAAA;IACX,yFAAY,CAAA;AACd,CAAC,EAHW,6BAA6B,KAA7B,6BAA6B,QAGxC;AAED;;GAEG;AACH,MAAM,CAAN,IAAY,8BAIX;AAJD,WAAY,8BAA8B;IACxC,qFAAS,CAAA;IACT,qGAAiB,CAAA;IACjB,qFAAS,CAAA;AACX,CAAC,EAJW,8BAA8B,KAA9B,8BAA8B,QAIzC","sourcesContent":["import { StyleProp, ViewStyle } from 'react-native';\n\nexport type AppleAuthenticationButtonProps = {\n onPress: () => void;\n buttonType: AppleAuthenticationButtonType;\n buttonStyle: AppleAuthenticationButtonStyle;\n cornerRadius?: number;\n style?: StyleProp<ViewStyle>;\n};\n\n/**\n * The options you can supply when making a call to `AppleAuthentication.signInAsync()`. None of\n * these options are required.\n *\n * @see [Apple\n * Documentation](https://developer.apple.com/documentation/authenticationservices/asauthorizationopenidrequest)\n * for more details.\n */\nexport type AppleAuthenticationSignInOptions = {\n /**\n * The scope of personal information to which your app is requesting access. The user can choose\n * to deny your app access to any scope at the time of logging in.\n * @defaults `[]` (no scopes).\n */\n requestedScopes?: AppleAuthenticationScope[];\n\n /**\n * Data that's returned to you unmodified in the corresponding credential after a successful\n * authentication. Used to verify that the response was from the request you made. Can be used to\n * avoid replay attacks.\n */\n state?: string;\n\n /**\n * Data that is used to verify the uniqueness of a response and prevent replay attacks.\n */\n nonce?: string;\n};\n\n/**\n * The options you can supply when making a call to `AppleAuthentication.refreshAsync()`. You must\n * include the ID string of the user whose credentials you'd like to refresh.\n *\n * @see [Apple\n * Documentation](https://developer.apple.com/documentation/authenticationservices/asauthorizationopenidrequest)\n * for more details.\n */\nexport type AppleAuthenticationRefreshOptions = {\n user: string;\n\n /**\n * The scope of personal information to which your app is requesting access. The user can choose\n * to deny your app access to any scope at the time of refreshing.\n * @defaults `[]` (no scopes).\n */\n requestedScopes?: AppleAuthenticationScope[];\n\n /**\n * Data that's returned to you unmodified in the corresponding credential after a successful\n * authentication. Used to verify that the response was from the request you made. Can be used to\n * avoid replay attacks.\n */\n state?: string;\n};\n\n/**\n * The options you can supply when making a call to `AppleAuthentication.signOutAsync()`. You must\n * include the ID string of the user to sign out.\n *\n * @see [Apple\n * Documentation](https://developer.apple.com/documentation/authenticationservices/asauthorizationopenidrequest)\n * for more details.\n */\nexport type AppleAuthenticationSignOutOptions = {\n user: string;\n\n /**\n * Data that's returned to you unmodified in the corresponding credential after a successful\n * authentication. Used to verify that the response was from the request you made. Can be used to\n * avoid replay attacks.\n */\n state?: string;\n};\n\n/**\n * The user credentials returned from a successful call to `AppleAuthentication.signInAsync()`,\n * `AppleAuthentication.refreshAsync()`, or `AppleAuthentication.signOutAsync()`.\n *\n * @see [Apple\n * Documentation](https://developer.apple.com/documentation/authenticationservices/asauthorizationappleidcredential)\n * for more details.\n */\nexport type AppleAuthenticationCredential = {\n /**\n * An identifier associated with the authenticated user. You can use this to check if the user is\n * still authenticated later. This is stable and can be shared across apps released under the same\n * development team. The same user will have a different identifier for apps released by other\n * developers.\n */\n user: string;\n\n /**\n * An arbitrary string that your app provided as `state` in the request that generated the\n * credential. Used to verify that the response was from the request you made. Can be used to\n * avoid replay attacks.\n */\n state: string | null;\n\n /**\n * The user's name. May be `null` or contain `null` values if you didn't request the `FULL_NAME`\n * scope, if the user denied access, or if this is not the first time the user has signed into\n * your app.\n */\n fullName: AppleAuthenticationFullName | null;\n\n /**\n * The user's email address. Might not be present if you didn't request the `EMAIL` scope. May\n * also be null if this is not the first time the user has signed into your app. If the user chose\n * to withhold their email address, this field will instead contain an obscured email address with\n * an Apple domain.\n */\n email: string | null;\n\n /**\n * A value that indicates whether the user appears to the system to be a real person.\n */\n realUserStatus: AppleAuthenticationUserDetectionStatus;\n\n /**\n * A JSON Web Token (JWT) that securely communicates information about the user to your app.\n */\n identityToken: string | null;\n\n /**\n * A short-lived session token used by your app for proof of authorization when interacting with\n * the app's server counterpart. Unlike `user`, this is ephemeral and will change each session.\n */\n authorizationCode: string | null;\n};\n\n/**\n * An object representing the tokenized portions of the user's full name.\n */\nexport type AppleAuthenticationFullName = {\n namePrefix: string | null;\n givenName: string | null;\n middleName: string | null;\n familyName: string | null;\n nameSuffix: string | null;\n nickname: string | null;\n};\n\nexport type AppleAuthenticationRevokeListener = () => void;\n\n/**\n * Scopes you can request when calling `AppleAuthentication.signInAsync()` or\n * `AppleAuthentication.refreshAsync()`.\n *\n * @note Note that it is possible that you will not be granted all of the scopes which you request.\n * You will still need to handle null values for any fields you request.\n *\n * @see [Apple\n * Documentation](https://developer.apple.com/documentation/authenticationservices/asauthorizationscope)\n * for more details.\n */\nexport enum AppleAuthenticationScope {\n FULL_NAME = 0,\n EMAIL = 1,\n}\n\nexport enum AppleAuthenticationOperation {\n /**\n * An operation that depends on the particular kind of credential provider.\n */\n IMPLICIT = 0,\n LOGIN = 1,\n REFRESH = 2,\n LOGOUT = 3,\n}\n\n/**\n * The state of the credential when checked with `AppleAuthentication.getCredentialStateAsync()`.\n *\n * @see [Apple\n * Documentation](https://developer.apple.com/documentation/authenticationservices/asauthorizationappleidprovidercredentialstate)\n * for more details.\n */\nexport enum AppleAuthenticationCredentialState {\n REVOKED = 0,\n AUTHORIZED = 1,\n NOT_FOUND = 2,\n TRANSFERRED = 3,\n}\n\n/**\n * A value that indicates whether the user appears to be a real person. You get this in the\n * realUserStatus property of a `Credential` object. It can be used as one metric to help prevent\n * fraud.\n *\n * @see [Apple\n * Documentation](https://developer.apple.com/documentation/authenticationservices/asuserdetectionstatus)\n * for more details.\n */\nexport enum AppleAuthenticationUserDetectionStatus {\n UNSUPPORTED = 0,\n UNKNOWN = 1,\n LIKELY_REAL = 2,\n}\n\n/**\n * Controls the predefined text shown on the authentication button.\n */\nexport enum AppleAuthenticationButtonType {\n SIGN_IN = 0,\n CONTINUE = 1,\n}\n\n/**\n * Controls the predefined style of the authenticating button.\n */\nexport enum AppleAuthenticationButtonStyle {\n WHITE = 0,\n WHITE_OUTLINE = 1,\n BLACK = 2,\n}\n"]} | |
+ | |
diff --git a/node_modules/expo-apple-authentication/ios/EXAppleAuthentication.podspec b/node_modules/expo-apple-authentication/ios/EXAppleAuthentication.podspec | |
index 08a78d1..f9a48b8 100644 | |
--- a/node_modules/expo-apple-authentication/ios/EXAppleAuthentication.podspec | |
+++ b/node_modules/expo-apple-authentication/ios/EXAppleAuthentication.podspec | |
@@ -10,7 +10,7 @@ Pod::Spec.new do |s| | |
s.license = package['license'] | |
s.author = package['author'] | |
s.homepage = package['homepage'] | |
- s.platform = :ios, '10.0' | |
+ s.platform = :ios, '9.0' | |
s.source = { git: 'https://github.com/expo/expo.git' } | |
s.source_files = 'EXAppleAuthentication/**/*.{h,m}' | |
s.preserve_paths = 'EXAppleAuthentication/**/*.{h,m}' | |
diff --git a/node_modules/expo-apple-authentication/ios/EXAppleAuthentication/EXAppleAuthenticationRequest.m b/node_modules/expo-apple-authentication/ios/EXAppleAuthentication/EXAppleAuthenticationRequest.m | |
index 5e1c3c0..655b7c1 100644 | |
--- a/node_modules/expo-apple-authentication/ios/EXAppleAuthentication/EXAppleAuthenticationRequest.m | |
+++ b/node_modules/expo-apple-authentication/ios/EXAppleAuthentication/EXAppleAuthenticationRequest.m | |
@@ -41,6 +41,9 @@ - (void)performOperation:(ASAuthorizationProviderAuthorizationOperation)operatio | |
if (_options[@"state"]) { | |
request.state = _options[@"state"]; | |
} | |
+ if (_options[@"nonce"]) { | |
+ request.nonce = _options[@"nonce"]; | |
+ } | |
_authController = [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[request]]; | |
_authController.presentationContextProvider = self; | |
diff --git a/node_modules/expo-apple-authentication/src/AppleAuthentication.ts b/node_modules/expo-apple-authentication/src/AppleAuthentication.ts | |
index 945316f..952bb49 100644 | |
--- a/node_modules/expo-apple-authentication/src/AppleAuthentication.ts | |
+++ b/node_modules/expo-apple-authentication/src/AppleAuthentication.ts | |
@@ -80,8 +80,3 @@ export async function getCredentialStateAsync( | |
return ExpoAppleAuthentication.getCredentialStateAsync(user); | |
} | |
-const ExpoAppleAuthenticationEventEmitter = new EventEmitter(ExpoAppleAuthentication); | |
- | |
-export function addRevokeListener(listener: AppleAuthenticationRevokeListener): Subscription { | |
- return ExpoAppleAuthenticationEventEmitter.addListener('Expo.appleIdCredentialRevoked', listener); | |
-} | |
diff --git a/node_modules/expo-apple-authentication/src/AppleAuthentication.types.ts b/node_modules/expo-apple-authentication/src/AppleAuthentication.types.ts | |
index f2efb5b..8292863 100644 | |
--- a/node_modules/expo-apple-authentication/src/AppleAuthentication.types.ts | |
+++ b/node_modules/expo-apple-authentication/src/AppleAuthentication.types.ts | |
@@ -30,6 +30,11 @@ export type AppleAuthenticationSignInOptions = { | |
* avoid replay attacks. | |
*/ | |
state?: string; | |
+ | |
+ /** | |
+ * Data that is used to verify the uniqueness of a response and prevent replay attacks. | |
+ */ | |
+ nonce?: string; | |
}; | |
/** |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/node_modules/react-native-firebase/android/build.gradle b/node_modules/react-native-firebase/android/build.gradle | |
index b690efb..b567e0b 100644 | |
--- a/node_modules/react-native-firebase/android/build.gradle | |
+++ b/node_modules/react-native-firebase/android/build.gradle | |
@@ -1,3 +1,7 @@ | |
+def safeExtGet(prop, fallback) { | |
+ rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback | |
+} | |
+ | |
buildscript { | |
repositories { | |
google() | |
@@ -182,7 +186,12 @@ dependencies { | |
* -------------------------------- */ | |
// For React Native Firebase Notifications | |
- api "com.android.support:support-v4:$supportVersion" | |
+ def supportLibVersion = safeExtGet('supportLibVersion', '28.0.0') | |
+ def supportLibMajorVersion = supportLibVersion.split('\\.')[0] as int | |
+ def supportV4LibName = (supportLibMajorVersion < 20) ? "androidx.legacy:legacy-support-v4" : "com.android.support:support-v4" | |
+ def supportV4Version = safeExtGet('supportV4Version', safeExtGet('supportLibVersion', '$supportVersion')) | |
+ | |
+ api "$supportV4LibName:$supportV4Version" | |
// For React Native Firebase Notifications | |
compileOnly 'me.leolin:ShortcutBadger:1.1.22@aar' | |
diff --git a/node_modules/react-native-firebase/android/src/main/java/io/invertase/firebase/auth/RNFirebaseAuth.java b/node_modules/react-native-firebase/android/src/main/java/io/invertase/firebase/auth/RNFirebaseAuth.java | |
index d868862..11b5446 100644 | |
--- a/node_modules/react-native-firebase/android/src/main/java/io/invertase/firebase/auth/RNFirebaseAuth.java | |
+++ b/node_modules/react-native-firebase/android/src/main/java/io/invertase/firebase/auth/RNFirebaseAuth.java | |
@@ -813,6 +813,7 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule { | |
String provider, | |
String authToken, | |
String authSecret, | |
+ String authNonce, | |
final Promise promise | |
) { | |
FirebaseApp firebaseApp = FirebaseApp.getInstance(appName); | |
@@ -1293,6 +1294,7 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule { | |
String provider, | |
String authToken, | |
String authSecret, | |
+ String authNonce, | |
final Promise promise | |
) { | |
FirebaseApp firebaseApp = FirebaseApp.getInstance(appName); | |
@@ -1367,6 +1369,7 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule { | |
String provider, | |
String authToken, | |
String authSecret, | |
+ String authNonce, | |
final Promise promise | |
) { | |
FirebaseApp firebaseApp = FirebaseApp.getInstance(appName); | |
diff --git a/node_modules/react-native-firebase/dist/index.d.ts b/node_modules/react-native-firebase/dist/index.d.ts | |
index 39ef4c9..116f510 100644 | |
--- a/node_modules/react-native-firebase/dist/index.d.ts | |
+++ b/node_modules/react-native-firebase/dist/index.d.ts | |
@@ -9,6 +9,7 @@ declare module 'react-native-firebase' { | |
providerId: string; | |
token: string; | |
secret: string; | |
+ nonce?: string; | |
}; | |
type FirebaseModuleAndStatics<M, S = {}> = { | |
@@ -966,7 +967,7 @@ declare module 'react-native-firebase' { | |
type AuthProvider = { | |
PROVIDER_ID: string; | |
- credential: (token: string | null, secret?: string) => AuthCredential; | |
+ credential: (token: string | null, secret?: string, nonce?: string) => AuthCredential; | |
}; | |
type EmailAuthProvider = { | |
@@ -1206,6 +1207,7 @@ declare module 'react-native-firebase' { | |
OAuthProvider: AuthProvider; | |
TwitterAuthProvider: AuthProvider; | |
FacebookAuthProvider: AuthProvider; | |
+ AppleAuthProvider: AuthProvider; | |
PhoneAuthState: { | |
CODE_SENT: string; | |
AUTO_VERIFY_TIMEOUT: string; | |
diff --git a/node_modules/react-native-firebase/dist/modules/auth/User.js b/node_modules/react-native-firebase/dist/modules/auth/User.js | |
index 38d6905..e02a305 100644 | |
--- a/node_modules/react-native-firebase/dist/modules/auth/User.js | |
+++ b/node_modules/react-native-firebase/dist/modules/auth/User.js | |
@@ -102,7 +102,7 @@ export default class User { | |
linkWithCredential(credential) { | |
- return getNativeModule(this._auth).linkWithCredential(credential.providerId, credential.token, credential.secret).then(userCredential => this._auth._setUserCredential(userCredential)); | |
+ return getNativeModule(this._auth).linkWithCredential(credential.providerId, credential.token, credential.secret, credential.nonce).then(userCredential => this._auth._setUserCredential(userCredential)); | |
} | |
/** | |
* @deprecated Deprecated linkAndRetrieveDataWithCredential in favor of linkWithCredential. | |
@@ -112,7 +112,7 @@ export default class User { | |
linkAndRetrieveDataWithCredential(credential) { | |
console.warn('Deprecated linkAndRetrieveDataWithCredential in favor of linkWithCredential.'); | |
- return getNativeModule(this._auth).linkWithCredential(credential.providerId, credential.token, credential.secret).then(userCredential => this._auth._setUserCredential(userCredential)); | |
+ return getNativeModule(this._auth).linkWithCredential(credential.providerId, credential.token, credential.secret, credential.nonce).then(userCredential => this._auth._setUserCredential(userCredential)); | |
} | |
/** | |
* Re-authenticate a user with a third-party authentication provider | |
@@ -121,7 +121,7 @@ export default class User { | |
reauthenticateWithCredential(credential) { | |
- return getNativeModule(this._auth).reauthenticateWithCredential(credential.providerId, credential.token, credential.secret).then(userCredential => this._auth._setUserCredential(userCredential)); | |
+ return getNativeModule(this._auth).reauthenticateWithCredential(credential.providerId, credential.token, credential.secret, credential.nonce).then(userCredential => this._auth._setUserCredential(userCredential)); | |
} | |
/** | |
* Re-authenticate a user with a third-party authentication provider | |
@@ -133,7 +133,7 @@ export default class User { | |
reauthenticateAndRetrieveDataWithCredential(credential) { | |
console.warn('Deprecated reauthenticateAndRetrieveDataWithCredential in favor of reauthenticateWithCredential.'); | |
- return getNativeModule(this._auth).reauthenticateWithCredential(credential.providerId, credential.token, credential.secret).then(userCredential => this._auth._setUserCredential(userCredential)); | |
+ return getNativeModule(this._auth).reauthenticateWithCredential(credential.providerId, credential.token, credential.secret, credential.nonce).then(userCredential => this._auth._setUserCredential(userCredential)); | |
} | |
/** | |
* Reload the current user | |
diff --git a/node_modules/react-native-firebase/dist/modules/auth/index.js b/node_modules/react-native-firebase/dist/modules/auth/index.js | |
index 2fe260d..2d8283d 100644 | |
--- a/node_modules/react-native-firebase/dist/modules/auth/index.js | |
+++ b/node_modules/react-native-firebase/dist/modules/auth/index.js | |
@@ -20,6 +20,7 @@ import GithubAuthProvider from './providers/GithubAuthProvider'; | |
import OAuthProvider from './providers/OAuthProvider'; | |
import TwitterAuthProvider from './providers/TwitterAuthProvider'; | |
import FacebookAuthProvider from './providers/FacebookAuthProvider'; | |
+import AppleAuthProvider from './providers/AppleAuthProvider'; | |
const NATIVE_EVENTS = ['auth_state_changed', 'auth_id_token_changed', 'phone_auth_state_changed']; | |
export const MODULE_NAME = 'RNFirebaseAuth'; | |
export const NAMESPACE = 'auth'; | |
@@ -249,7 +250,7 @@ export default class Auth extends ModuleBase { | |
signInWithCredential(credential) { | |
- return getNativeModule(this).signInWithCredential(credential.providerId, credential.token, credential.secret).then(userCredential => this._setUserCredential(userCredential)); | |
+ return getNativeModule(this).signInWithCredential(credential.providerId, credential.token, credential.secret, credential.nonce).then(userCredential => this._setUserCredential(userCredential)); | |
} | |
/** | |
* Sign the user in with a third-party authentication provider | |
@@ -261,7 +262,7 @@ export default class Auth extends ModuleBase { | |
signInAndRetrieveDataWithCredential(credential) { | |
console.warn('Deprecated signInAndRetrieveDataWithCredential in favor of signInWithCredential.'); | |
- return getNativeModule(this).signInWithCredential(credential.providerId, credential.token, credential.secret).then(userCredential => this._setUserCredential(userCredential)); | |
+ return getNativeModule(this).signInWithCredential(credential.providerId, credential.token, credential.secret, credential.nonce).then(userCredential => this._setUserCredential(userCredential)); | |
} | |
/** | |
* Asynchronously signs in using a phone number. | |
@@ -473,6 +474,7 @@ export const statics = { | |
GithubAuthProvider, | |
TwitterAuthProvider, | |
FacebookAuthProvider, | |
+ AppleAuthProvider, | |
OAuthProvider, | |
PhoneAuthState: { | |
CODE_SENT: 'sent', | |
diff --git a/node_modules/react-native-firebase/dist/modules/auth/providers/AppleAuthProvider.js b/node_modules/react-native-firebase/dist/modules/auth/providers/AppleAuthProvider.js | |
new file mode 100644 | |
index 0000000..ce752cd | |
--- /dev/null | |
+++ b/node_modules/react-native-firebase/dist/modules/auth/providers/AppleAuthProvider.js | |
@@ -0,0 +1,24 @@ | |
+/** | |
+ * | |
+ * AppleAuthProvider representation wrapper | |
+ */ | |
+const providerId = 'apple.com'; | |
+export default class AppleAuthProvider { | |
+ constructor() { | |
+ throw new Error('`new AppleAuthProvider()` is not supported on the native Firebase SDKs.'); | |
+ } | |
+ | |
+ static get PROVIDER_ID() { | |
+ return providerId; | |
+ } | |
+ | |
+ static credential(token, nonce) { | |
+ return { | |
+ token, | |
+ secret: '', | |
+ providerId, | |
+ nonce | |
+ }; | |
+ } | |
+ | |
+} | |
diff --git a/node_modules/react-native-firebase/ios/RNFirebase/auth/RNFirebaseAuth.m b/node_modules/react-native-firebase/ios/RNFirebase/auth/RNFirebaseAuth.m | |
index 759e002..65de2a8 100644 | |
--- a/node_modules/react-native-firebase/ios/RNFirebase/auth/RNFirebaseAuth.m | |
+++ b/node_modules/react-native-firebase/ios/RNFirebase/auth/RNFirebaseAuth.m | |
@@ -495,6 +495,8 @@ RCT_EXPORT_METHOD(updatePhoneNumber: | |
(NSString *) authToken | |
secret: | |
(NSString *) authSecret | |
+ nonce: | |
+ (NSString *) authNonce | |
resolver: | |
(RCTPromiseResolveBlock) resolve | |
rejecter: | |
@@ -505,7 +507,7 @@ RCT_EXPORT_METHOD(updatePhoneNumber: | |
if (user) { | |
FIRPhoneAuthCredential *credential = | |
- (FIRPhoneAuthCredential *) [self getCredentialForProvider:provider token:authToken secret:authSecret]; | |
+ (FIRPhoneAuthCredential *) [self getCredentialForProvider:provider token:authToken secret:authSecret nonce:authNonce]; | |
if (credential == nil) { | |
return reject(@"auth/invalid-credential", | |
@@ -671,13 +673,15 @@ RCT_EXPORT_METHOD(signInWithCredential: | |
(NSString *) authToken | |
secret: | |
(NSString *) authSecret | |
+ nonce: | |
+ (NSString *) authNonce | |
resolver: | |
(RCTPromiseResolveBlock) resolve | |
rejecter: | |
(RCTPromiseRejectBlock) reject) { | |
FIRApp *firApp = [RNFirebaseUtil getApp:appDisplayName]; | |
- FIRAuthCredential *credential = [self getCredentialForProvider:provider token:authToken secret:authSecret]; | |
+ FIRAuthCredential *credential = [self getCredentialForProvider:provider token:authToken secret:authSecret nonce:authNonce]; | |
if (credential == nil) { | |
return reject(@"auth/invalid-credential", | |
@@ -1012,6 +1016,7 @@ RCT_EXPORT_METHOD(_confirmVerificationCode: | |
@param NSString provider | |
@param NSString authToken | |
@param NSString authSecret | |
+ @param NSString authNonce | |
@param RCTPromiseResolveBlock resolve | |
@param RCTPromiseRejectBlock reject | |
@return | |
@@ -1024,12 +1029,14 @@ RCT_EXPORT_METHOD(linkWithCredential: | |
(NSString *) authToken | |
authSecret: | |
(NSString *) authSecret | |
+ authNonce: | |
+ (NSString *) authNonce | |
resolver: | |
(RCTPromiseResolveBlock) resolve | |
rejecter: | |
(RCTPromiseRejectBlock) reject) { | |
FIRApp *firApp = [RNFirebaseUtil getApp:appDisplayName]; | |
- FIRAuthCredential *credential = [self getCredentialForProvider:provider token:authToken secret:authSecret]; | |
+ FIRAuthCredential *credential = [self getCredentialForProvider:provider token:authToken secret:authSecret nonce:authNonce]; | |
if (credential == nil) { | |
return reject(@"auth/invalid-credential", | |
@@ -1104,13 +1111,15 @@ RCT_EXPORT_METHOD(reauthenticateWithCredential: | |
(NSString *) authToken | |
authSecret: | |
(NSString *) authSecret | |
+ authNonce: | |
+ (NSString *) authNonce | |
resolver: | |
(RCTPromiseResolveBlock) resolve | |
rejecter: | |
(RCTPromiseRejectBlock) reject) { | |
FIRApp *firApp = [RNFirebaseUtil getApp:appDisplayName]; | |
- FIRAuthCredential *credential = [self getCredentialForProvider:provider token:authToken secret:authSecret]; | |
+ FIRAuthCredential *credential = [self getCredentialForProvider:provider token:authToken secret:authSecret nonce: authNonce]; | |
if (credential == nil) { | |
return reject(@"auth/invalid-credential", | |
@@ -1171,15 +1180,18 @@ RCT_EXPORT_METHOD(fetchSignInMethodsForEmail: | |
@param provider string | |
@param authToken string | |
@param authTokenSecret string | |
+ @param authNonce string | |
@return FIRAuthCredential | |
*/ | |
-- (FIRAuthCredential *)getCredentialForProvider:(NSString *)provider token:(NSString *)authToken secret:(NSString *)authTokenSecret { | |
+- (FIRAuthCredential *)getCredentialForProvider:(NSString *)provider token:(NSString *)authToken secret:(NSString *)authTokenSecret nonce:(NSString *)authNonce{ | |
FIRAuthCredential *credential; | |
if ([provider compare:@"twitter.com" options:NSCaseInsensitiveSearch] == NSOrderedSame) { | |
credential = [FIRTwitterAuthProvider credentialWithToken:authToken secret:authTokenSecret]; | |
} else if ([provider compare:@"facebook.com" options:NSCaseInsensitiveSearch] == NSOrderedSame) { | |
credential = [FIRFacebookAuthProvider credentialWithAccessToken:authToken]; | |
+ } else if ([provider compare:@"apple.com" options:NSCaseInsensitiveSearch] == NSOrderedSame) { | |
+ credential = [FIROAuthProvider credentialWithProviderID:@"apple.com" IDToken:authToken rawNonce:authNonce]; | |
} else if ([provider compare:@"google.com" options:NSCaseInsensitiveSearch] == NSOrderedSame) { | |
credential = [FIRGoogleAuthProvider credentialWithIDToken:authToken accessToken:authTokenSecret]; | |
} else if ([provider compare:@"password" options:NSCaseInsensitiveSearch] == NSOrderedSame) { |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
1- first install everything | |
1a - unimodules from expo, they have thorough documentation for it | |
1b - expo-apple-authentication also from expo | |
1c - patch-package (google it, it's required to patch things so this works | |
1d - react-native-firebase v5 (v6 will have different paths etc., sorry) | |
1e - react-native-sha256 (for hashing the nonce you use to prevent replay attacks during the OAuth triangle, of course) | |
1f - don't forget to run pod install like I did, right? | |
2 - configure everything | |
2a - wow, the amount of apple config. the entitlement, provisioning profiles (use fastlane, it's worth it), email sender reg, callback handler, domain verification | |
2b - firebase config - enable the sign-in method, use all the info from above | |
3 - install the 2 patches here in your project patches directory, one patch each for expo-apple-authentication@1.0.0 and react-native-firebase@5.5.6 | |
4 - do a quick test, make sure it works. I put a big chunk of code from my project here and not all there method references will work, but you'll see the flow | |
5 - profit? |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I believe the react-native-firebase patches are all integrated now with v5.5.6 (or v6.2.0) and I think expo released apple authentication with the nonce pass through.
So I think currently you just need to take care of generating a random string (the nonce) and using react-native-sha256 to get the checksum for it, and use that between expo-apple-authentication + react-native-firebase and it will work