Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mikehardy/83c9535d71cec4a8764bfda5a492c25f to your computer and use it in GitHub Desktop.
Save mikehardy/83c9535d71cec4a8764bfda5a492c25f to your computer and use it in GitHub Desktop.
react-native-firebase + apple sign-in (via expo-apple-authentication
jest.mock('@unimodules/core', () => {
return {
EventEmitter: jest.fn().mockImplementation(() => {
return {};
}),
NativeModulesProxy: jest.fn(),
requireNativeViewManager: jest.fn(),
};
});
/* 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);
}
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;
};
/**
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) {
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?
@mikehardy
Copy link
Author

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment