React Native Read User Email Without Prompting - android

If I install an app on Google Play I notice that it informs me about what permissions the app requires. Once it does it in the app it also prompts me. This is all very fine.
I would like to just read the Google or Apple account e-mail - without prompting the end user for an e-mail.
I don't want to use the email as an auth token, but prefill a "Subscribe to News Letter" field. The user can then toggle ON or OFF (or choose to add another email).

If I'm getting the question right,
You need to get the primary email address of the currently signed in user, which can be used to pre-fill some kind of a form (eg. sign-up form)
For Android
Try the following react native wrapper library react-native-account-manager
Once you have setup using the instructions on the readme from the above link, use the following code to retrieve the list of signed in google accounts.
Please note this could result in an empty list, if there are no google accounts associated with the device
import AccountManager from 'react-native-account-manager';
AccountManager.getAccountsByType('accountName').then((accounts) => {
// console.log('available accounts', accounts);
let [firstAccount] = accounts;
AccountManager.getUserData(firstAccount, 'storedKey').then((storedData) => {
// console.log('stored data for storeKey', storedData);
AccountManager.setUserData(account, 'storedKey', JSON.stringify({foo: "bar"})).then(() => {
// console.log('data successfully stored');
})
});
})
For iOS
Bad luck
Apple does NOT expose the iPhone user’s details like their email address, password or credit card details to the app developer.
An alternative is to use the icloud token to create the user. You could use the following guide to do it
For a wrapper on obtaining a iCloud token using react-native use react-native-icloud-user-tokenlibrary
Kudos

Related

How to prevent login until user verified by the admin in Firebase?

I am developing android native application using android studio and firebase. i did admin panel website for the client app. user have to register the client app usually but they have to wait until the admin verification. how to do it ?
If you want to prevent a user declared in the Auth service to be able to login, the most straightforward way is to disable his account.
With the Admin SDK you can create users that are initially disabled. For example, with the Admin SDK for Node.js:
admin.auth().createUser({
email: 'user#example.com',
password: 'secretPassword',
disabled: true
})
.then(function(userRecord) {
// See the UserRecord reference doc for the contents of userRecord.
console.log('Successfully created new user:', userRecord.uid);
})
.catch(function(error) {
console.log('Error creating new user:', error);
});
See here for more details. So you could implement that in a Cloud Function that your admin panel website calls for creating a new user.
Then in a app/module dedicated to the Admin users, you would call another Cloud Function that would update the user record in order to activate a user, as follows:
admin.auth().updateUser(uid, {
disabled: false
})
.then(function(userRecord) {
// See the UserRecord reference doc for the contents of userRecord.
console.log('Successfully updated user', userRecord.toJSON());
})
.catch(function(error) {
console.log('Error updating user:', error);
});
You may be interested by this article which presents how to build, with a Callable Cloud Function, a module for allowing end-users with a specific Admin role creating other users (disclaimer, I'm the author).
Update following your comment
If your users register themselves from the app, you should note that on successful account creation they are immediately logged in (see the doc).
In this case, the classical solution is to use a Custom Claim which indicates if the user has been approved/verified by the Admin.
If the user holds this Custom Claim you show him the corresponding application screens. If not, you can, for example, display a message or/and sign them out and send them back to the login screen... (it’s up to you to define the exact flow). In parallel, you write your Securities Rules for Firestore, Cloud Storage, etc based on the Claim. See the Custom Claims documentation for more details on how to implement that.

Firebase Auth using phone number and password

I am developing Android app using Firebase. Because of that, I want to use Firebase Auth. I have following requirements:
Register/Log in using Facebook
Register/Log in using Email/Password
Register/Log in using Phone Number/Password
The first two are OK, I followed basic tutorials. However, Phone Number / Password is the problem here. Firebase supports only Phone Number/SMS Token for this (its called Phone Auth), but there is no mention about my case. I do not want to hack Firebase and use its realtime database instead of Auth 'database'. Is there any better way to achieve this?
Thank you.
If you have both email and phone of your user and you can use Admin SDK, then perhaps you could exchange users phone number to his email and login with email and password in the background.
Something like this (node.js)
admin.auth().getUserByPhoneNumber(phoneNumber)
.then(user => {
firebase.auth().signInWithEmailAndPassword(user.email, password);
});
Firebase phone authentication is using OTP(one time password). This way there is no hassle for the user to remember the password. Once authenticated, you will be registered. The sms code acts as a password. But that is for one time. Usually , users prefer such behaviour in which you dont have to remember the passwords. If you are still looking for the way you want, see this link and create a custom authentication method.
https://firebase.google.com/docs/auth/android/custom-auth
I had a similar problem -
I combined firebase auth(email + password) with (phone+otp) to get phone+password auth -
https://medium.com/#shivampesitbng/firebase-phone-password-auth-in-vue-b94f15b8fb3d
Use Fake Email:
Well, Firebase doesn't support sign in with mobile number and password but it supports email and password. So you can create a fake email with your mobile number.
Ie: 78******69#yourdomain.com
Also, you can create a complete Authentication system using it.
Registration:
Input user mobile and password and proceed to the next page.
Now use Firebase Phone Auth (OTP) to createUser. If process success, link fake email, password credentials in background.
AuthCredential credential = EmailAuthProvider.getCredential(email, password);
auth.getCurrentUser().linkWithCredential(credential);
Login:
Input mobile and password to login. Convert the mobile in fake email and then signInWithEmailAndPassword().
Forget Password:
Redirect the user to a new Page and user Phone Auth to verify the user. If successful, input a new password and change the password of the Email Auth.

Firebase Authentication with Google issue

I have been using Firebase Authentication in my app, and have noticed an issue with a particular use case.
I have enabled account linking sign up flow for my app, and thus I can attach multiple providers associated with a single email address.
Scenario 1: (Works fine)
The user has signed up with Google initially and sometime later, signs in in with Facebook or registers with email and password.
The account linking works fine and Facebook and/or Email is added in the provider list.
So, I can have 2 or 3 providers for the email, Google (initially), Facebook and Password (after that).
Scenario 2: (The bug)
The user has signed up with Facebook and/or Email initially and later signs in with Google, now the account linking doesn't work. Google replaces the previous providers present.
Account linking fails, and I just have Google as the sole provider associated with the email address and the others are gone.
In the second scenario, while signing in with Google, it should fail and throw FirebaseAuthCollisionException but it doesn't and succeeds. This is the main issue.
I can't paste the whole code here, but just a snippet for sure.
firebaseAuth
.signInWithCredential(credential)
.addOnFailureListener(exception -> {
if (exception instanceof FirebaseAuthUserCollisionException) {
mCredentialToLinkWith = credential;
if (mProviderList.size() == 1) {
if (mProviderList.contains(EmailAuthProvider.PROVIDER_ID)) {
mRegisterProviderPresenter.linkWithEmailProvider(credential, email);
} else {
linkProviderAccounts(email, AuthenticationHelper.getProviderToLinkAccounts(mWeakActivity, mProviderList));
}
} else {
linkProviderAccounts(email, AuthenticationHelper.getProviderToLinkAccounts(mWeakActivity, mProviderList));
}
} else {
Timber.d("Failed in signInWithCredential and unexpected exception %s", exception.getLocalizedMessage());
mRegisterProviderPresenter.onRegistrationFailed(new ErrorBundle(ErrorBundle.FIREBASE_ERROR, exception.getLocalizedMessage()));
}
})
.addOnSuccessListener(authResult -> {
Timber.d("Success: signInCred");
FirebaseUser firebaseUser = authResult.getUser();
/**
* Store the user details only for first time registration
* and not while acc linking
*/
storeUserCredentials(firebaseUser);
AuthenticationHelper.logUserDetails(firebaseUser);
mRegisterProviderPresenter.onRegistrationSuccess(mAlreadyRegistered);
});
Hope someone can come up with some help.
Facebook is a social identity provider and it doesn't own the emails. If an email is hacked, Facebook can't detect it and disable the account registered by this email. While Google is an email provider, its accounts are considered to be more secure.
Based on this theory, scenario 2 is different from 1. In scenario 1, the user has proved the ownership of this email by signing with Google first. So the user is allowed to add Facebook account using the same email. In scenario 2, Facebook sign in happens first and this provider record is untrusted, so it's removed when user signs in with another trusted provider.
Your code behavior is correct in both scenarios.
I faced the same issue and this is a supplemental answer for the question in the comment i.e.
Why is that after initially registering with a email & password, and then with Google, Google still replaces it?
I did some more exploration and found the answer here.
Pasting the relevant snippet.
If there is an existing account with the same email address but created with non-trusted credentials (e.g. non-trusted provider or password), the previous credentials are removed for security reason. A phisher (who is not the email address owner) might create the initial account - removing the initial credential would prevent the phisher from accessing the account afterwards.
The solution to handle this, i.e. to prevent Google from replacing the existing provider with Google, is to verify the email of the user.
So, after the user creates the account with email & password, or logs in with Facebook (or any other provider), send an email verification link to the user.
After the user verifies his/her email, then the subsequent Sign-in with Google will NOT replace the existing providers.
just use the email and password auth for the moment or a 3rd party plugin no solution so far

How to do a email verification to firebase user for signup and email update using FirebaseAuth? [duplicate]

Question says it all. In Firebase, how do I confirm email when a user creates an account, or, for that matter, do password reset via email.
I could ask more broadly: is there any way to send emails out from Firebase? E.g. notifications, etc. This isn't the kind of thing you would usually do client-side.
Update
Note that this was never a very secure way of handling email verification, and since Firebase now supports email verification, it should probably be used instead.
Original answer
I solved the email verification using the password reset feature.
On account creation I give the user a temporary (randomly generated) password. I then trigger a password reset which will send an email to the user with a link. The link will allow the user to set a new password.
To generate a random password you can use code similar to this:
function () {
var possibleChars = ['abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!?_-'];
var password = '';
for(var i = 0; i < 16; i += 1) {
password += possibleChars[Math.floor(Math.random() * possibleChars.length)];
}
return password;
}
Note that this is happening on the client, so a malicious user could tamper with your logic.
This would need to be done outside of firebase. I store users at /users/ and keep a status on them (PENDING, ACTIVE, DELETED). I have a small service that monitors users of a PENDING status and sends out a confirmation email. Which has a link to a webservice I've created to update the user status to ACTIVE.
[Engineer at Firebase - Update 2014-01-27]
Firebase Simple Login now supports password resets for email / password authentication.
Each of the Simple Login client libraries has been given a new method for generating password reset emails for the specified email address - sendPasswordResetEmail() on the Web and Android, and sendPasswordResetForEmail() on iOS.
This e-mail will contain a temporary token that the user may use to log into their account and update their credentials. This token will expire after 24 hours or when the user changes their password, whichever occurs first.
Also note that Firebase Simple Login enables full configuration of the email template as well as the sending address (including whitelabel email from your domain for paid accounts).
To get access to this feature, you'll need to update your client library to a version of v1.2.0 or greater. To grab the latest version, check out https://www.firebase.com/docs/downloads.html.
Also, check out https://www.firebase.com/docs/security/simple-login-email-password.html for the latest Firebase Simple Login - Web Client docs.
As at 2016 July, you might not have to use the reset link etc. Just use the sendEmailVerification() and applyActionCode functions:
In short, below is basically how you'll approach this, in AngularJS:
// thecontroller.js
$scope.sendVerifyEmail = function() {
console.log('Email sent, whaaaaam!');
currentAuth.sendEmailVerification();
}
// where currentAuth came from something like this:
// routerconfig
....
templateUrl: 'bla.html',
resolve: {
currentAuth:['Auth', function(Auth) {
return Auth.$requireSignIn() // this throws an AUTH_REQUIRED broadcast
}]
}
...
// intercept the broadcast like so if you want:
....
$rootScope.$on("$stateChangeError", function(event, toState, toParams, fromState, fromParams, error) {
if (error === "AUTH_REQUIRED") {
$state.go('login', { toWhere: toState });
}
});
....
// So user receives the email. How do you process the `oobCode` that returns?
// You may do something like this:
// catch the url with its mode and oobCode
.state('emailVerify', {
url: '/verify-email?mode&oobCode',
templateUrl: 'auth/verify-email.html',
controller: 'emailVerifyController',
resolve: {
currentAuth:['Auth', function(Auth) {
return Auth.$requireSignIn()
}]
}
})
// Then digest like so where each term is what they sound like:
.controller('emailVerifyController', ['$scope', '$stateParams', 'currentAuth', 'DatabaseRef',
function($scope, $stateParams, currentAuth, DatabaseRef) {
console.log(currentAuth);
$scope.doVerify = function() {
firebase.auth()
.applyActionCode($stateParams.oobCode)
.then(function(data) {
// change emailVerified for logged in User
console.log('Verification happened');
})
.catch(function(error) {
$scope.error = error.message;
console.log(error.message, error.reason)
})
};
}
])
And ooh, with the above approach, I do not think there's any need keeping the verification of your user's email in your user data area. The applyActionCode changes the emailVerified to true from false.
Email verification is important when users sign in with the local account. However, for many social authentications, the incoming emailVerified will be true already.
Explained more in the article Email Verification with Firebase 3.0 SDK
What I did to work around this was use Zapier which has a built in API for firebase. It checks a location for added child elements. Then it takes the mail address and a verification url from the data of new nodes and sends them forwards. The url points back to my angular app, which sets the user email as verified.
As I host my app files in firebase, I don't need have to take care of any servers or processes doing polling in the background.
There is a delay, but as I don't block users before verifying mails it's ok. Zapier has a free tier and since I don't have much traffic it's a decent workaround for time being.
The new Firebase SDK v3 appears to support email address verification, see here (put your own project id in the link) but it doesn't appear to be documented yet.
I have asked the question on SO here
See #SamQuayle's answer there with this link to the official docs.
As noted by various others Firebase does now support account related emails but even better, as of 10 days ago or so it also supports sending any kind of email via Firebase Functions. Lots of details in the docs and example code here.
I used following code to check the email verification after creating new account.
let firAuth = FIRAuth.auth()
firAuth?.addAuthStateDidChangeListener { auth, user in
if let loggedUser = user {
if loggedUser.emailVerified == false {
loggedUser.sendEmailVerificationWithCompletion({ (error) in
print("error:\(error)")
})
}
else {
print(loggedUser.email)
}
} else {
// No user is signed in.
print("No user is signed in.")
}
}
I used MandrillApp. You can create an API key that only allows sending of a template. This way even thought your key is exposed it can't really be abused unless someone wants to fire off tonnes of welcome emails for you.
That was a hack to get myself off the ground. I'm now enabling CORS from a EC2 that uses the token to verify that the user exists before extending them a welcome via SES.

How do I restrict Google App Engine Endpoints API access to only my Android applications?

I am an Android developer building my first Google App Engine (java) back-end for my apps. I don't want anybody else to access this API other than my app. (I plan to use App engine for verifying InApp purchases in my Android app). My data is not relevant to users so,
I don't want users to be able to access my API even if they are logged in with their Google accounts (on web or Android devices).
I followed the steps mentioned in - "Specifying authorized clients in the API backend"
(https://developers.google.com/appengine/docs/java/endpoints/auth)
like generating client IDs and add them in #Api (clientIds and audiences)
except "Add a User parameter" - since I don't need user authentication.
Then I deployed App engine and I am still able to access the API through API explorer (https://your_app_id.appspot.com/_ah/api/explorer)
(I haven't added API_EXPLORER client ID)
I tested with the APK that was built with the endpoint libs before adding client IDs and can still access the API.
Is adding a "User parameter" to all endpoint APIs a must? to achieve my purpose (restrict API to only my Android apps).
Can I pass null as userAccount name from Android client and ignore user parameter value on server (since it will be null)? Will this ensure that the API is accessible only from my android apps (since the client ID is generated for my package name and SHA1 of the APK?)
Should I use something like a service account for this purpose?
The documentation says for Android, both Android and Web client IDs must be added and audience must be the same as web client ID. Does this open access to any other web client? can I skip mentioning web client ID and still achieve my purpose?
Appreciate your time and help.
...... updating with my further investigation ...
I did the following:
Added User parameter to APIs on backend - but did not check for null value. API can still be accessed without passing any credentials (from Android debug APK and API explorer)
Then, I tried
mCredential = GoogleAccountCredential.usingAudience(this, "server:client_id:" + WEB_CLIENT_ID);
mCredential.setSelectedAccountName(null);
and passed this credential to API builder (as suggested in some other posts)
Caused FATAL EXCEPTION. So, we can't pass null account name.
I could call the API using API explorer without OAuth. But when I enabled OAuth, it gave error saying this client ID is not allowed! ( I haven't yet added com.google.api.server.spi.Constant.API_EXPLORER_CLIENT_ID in client_ids{})
Then I added code to throw OAuthRequestException on the backend if the user is null. This resulted in API explorer getting errors without OAuth. It works with OAuth enabled after adding API_EXPLORER_CLIENT_ID to client_ids)
Added code to pass valid user account name(email) from my Android app. Then, I am able to access API only with my release APK. Even the debug APK gets exceptions! - which is what I expected..So, I assume no other Android apps will be able to access this API.
So, not checking for null user on back-end API is a bad idea (as suggested in other posts). It is as good as not mentioning any client_ids and not having User param.
Only question I have at this moment is: If some one can figure out the WEB_CLIENT_ID from the APK, will they be able to use it to build a web client to access my API (I haven't mentioned client secret anywhere in the code. So I am thinking this is not possible).
I did search Google groups and Stackoverflow, but still it is not clear.
(Authenticate my “app” to Google cloud endpoints not a “user”)
Authenticate my "app" to Google Cloud Endpoints not a "user"
(How do I protect my API that was built using Google Cloud Endpoints?)
How do I protect my API that was built using Google Cloud Endpoints?
(Restrict access to google cloud endpoints to Android app)
Restrict access to google cloud endpoints to Android app
I had a similar issue, not between Android and App Engine, but between a separate server and App Engine. The way I handled it was to add a signature hash field as a parameter to each API call. If the request had an improper signature, it would be denied.
For example, suppose your API end-point is example.com/api/do_thing?param1=foo. I would hash the entire url, along with a secret key, and then append the result of the hash to the request: example.com/api/do_thing?param1=foo&hash=[some long hex value].
Then, on the server side, I would first remove the hash from the url request, then run the hash on everything that was remaining. Finally, you check whether the calculated hash matches the one that was sent with the request and if they don't, you can deny the request.
It is very important however that your secret key remain secret. You have to be careful with this on Android because someone could attempt to decompile your APK.
Facing the same problem than you ! Authenticate Android End point without Google User Account is just impossible !
So here is my way to resolv this problem, without any user interaction (Maybe not the right but that works, and you've got strong authentication (SHA1 + Google Account)):
HERE IS MY ANDROID CODE
Get and Build Valid Credential
//Get all accounts from my Android Phone
String validGoogleAccount = null;
Pattern emailPattern = Patterns.EMAIL_ADDRESS; // API level 8+
Account[] accounts = AccountManager.get(context).getAccounts();
for (Account account : accounts) {
if (emailPattern.matcher(account.name).matches()) {
//Just store mail if countain gmail.com
if (account.name.toString().contains("gmail.com")&&account.type.toString().contains("com.google")){
validGoogleAccount=account.name.toString();
}
}
}
//Build Credential with valid google account
GoogleAccountCredential credential = GoogleAccountCredential.usingAudience(this,"server:client_id:301991144702-5qkqclsogd0b4fnkhrja7hppshrvp4kh.apps.googleusercontent.com");
credential.setSelectedAccountName(validGoogleAccount);
Use this credential for secure calls
Campagneendpoint.Builder endpointBuilder = new Campagneendpoint.Builder(AndroidHttp.newCompatibleTransport(), new JacksonFactory(), credential);
HERE IS MY API BACKEND CODE:
API Annotation
#Api(
scopes=CONSTANTES.EMAIL_SCOPE,
clientIds = {CONSTANTES.ANDROID_CLIENT_ID,
CONSTANTES.WEB_CLIENT_ID,
com.google.api.server.spi.Constant.API_EXPLORER_CLIENT_ID},
audiences = {CONSTANTES.ANDROID_AUDIENCE},
name = "campagneendpoint",
version = "v1"
)
Method code:
public Collection<Campagne> getCampagnes(#Named("NumPortable")String NumPortable, User user) throws UnauthorizedException {
if (user == null) throw new UnauthorizedException("User is Not Valid");
return CampagneCRUD.getInstance().findCampagne(NumPortable);
}
For the moment, it only works on Android (I don't know how we gonna do on IOS..)..
Hope It will help you !
Google provides ways to do this for Android, web and iOS
The steps involves:
Specifying a client Id for apps you want to allow to make requests to your API
Adding a User parameter to all exposed methods to be protected by authorization.
Generating the client library again for any Android clients
Redeploying your backend API.
Updating the regenerated jar file to your Android project for your Android client.
These steps are laid out in clear detail on Google's Using Auth with Endpoints and also on this blog
Facing the same problem, here are the result of my research :
Added Android cliend id with SHA1 fingerprint in Google console
Use of it in the API annotation
BUT :
If i dont add user parameter to methods : the check about android app client id does not work
If I add the USER parameter but do not ask the user to choose its google account to create the credential ... also it does not work ...
Conclusion : It seems to be mandatory to connect a user account for the check about the app client id to be executed ... I really do not understand why because no link exist between the 2 processes
Access this site
Choose your project, go to credentials section
Create a new api key
Create a new android key
Click on "Edit allowed android applications" and enter your SHA1 key; your android package name
Let me know if this solves the issues.

Categories

Resources