Security Sign-in Mail using Firebase - firebase

How do I send a Security Sign-in mail to the users using firebase?
For example, your account was accessed at 3:21 pm, 16th Dec 2021.
Platform : Mac OS X, Chrome 96.0.4664
IP address: xxxxxxxxxxx
Location: xyz.
Thanks in advance.

Firebase doesn't have any feature built in for sending such sign-in alerts. It doesn't even have a way to build it on top of its existing providers, as there no server-side event when a user signs in.
The only option I can think of to implement this within Firebase is to implement your own custom sign-in provider, and then send a message from there.
It also sounds like an interesting feature, so I recommend you file a feature request for it.

I have come up with an implementation.
I don't know if it is wise to execute this client-side (Can also be done using Cloud Functions).
authResult = await auth.signInWithEmailAndPassword(
email: email,
password: password,
);
final Map<String, dynamic> device = await getDeviceInfo();
final Map<String, dynamic> net = await getNetworkInfo();
final DateTime time = DateTime.now();
//I am using Trigger Email Extension here.
await mail.doc().set({
'to': email.toLowerCase(),
'message': {
'subject': 'Succesful Login',
'text':
"Welcome back! You have successfully logged in to XYZ. You can now start using the app. Email = $email Platform: ${device["name"]} Time: $time IP Address: ${net["wifiIP"]}",
},
});
These are the functions mentioned above:
import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:network_info_plus/network_info_plus.dart';
Future<Map<String, dynamic>> getDeviceInfo() async {
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
if (Platform.isIOS) {
IosDeviceInfo iosInfo = await deviceInfo.iosInfo;
return iosInfo.toMap();
} else if (Platform.isAndroid) {
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
return androidInfo.toMap();
} else {
WebBrowserInfo webBrowserInfo = await deviceInfo.webBrowserInfo;
return webBrowserInfo.toMap();
}
}
Future<Map<String, dynamic>> getNetworkInfo() async {
final info = NetworkInfo();
var wifiName = await info.getWifiName(); // FooNetwork
var wifiBSSID = await info.getWifiBSSID(); // 11:22:33:44:55:66
var wifiIP = await info.getWifiIP(); // 192.168.1.43
var wifiIPv6 =
await info.getWifiIPv6(); // 2001:0db8:85a3:0000:0000:8a2e:0370:7334
var wifiSubmask = await info.getWifiSubmask(); // 255.255.255.0
var wifiBroadcast = await info.getWifiBroadcast(); // 192.168.1.255
var wifiGateway = await info.getWifiGatewayIP(); // 192.168.1.1
return {
'wifiName': wifiName,
'wifiBSSID': wifiBSSID,
'wifiIP': wifiIP,
'wifiIPv6': wifiIPv6,
'wifiSubmask': wifiSubmask,
'wifiBroadcast': wifiBroadcast,
'wifiGateway': wifiGateway,
};
}
Trigger Email

Related

Flutter new phone authentication now do "not a robot" check in seperate browser which make entire procedure slow

I solved this issue (App crashes upon phone authentication after changing package name - Flutter) of app crash by adding implementation "androidx.browser:browser:1.2.0" into app/build.gradle dependencies.
But NOW whole phone authentication procedure got changed. Now app open a browser to do Not a robot test. But I don't want app to open a browser just to verify it's not a robot it make entire process slow and ugly. Below is the video example. How to get rid of this issue? It shows app firebase address in the browser link too.
Video example of issue is below
https://drive.google.com/file/d/1G7noQWyyAHvyTo_Te0v6d2O3IvaiClAw/view?usp=sharing
Below is the code snippet of verifyPhone function.
Future<dynamic> verifyPhone(phoneNo, BuildContext context) async {
var completer = Completer<dynamic>();
dynamic newUserResult;
Future<String> getOTPresult() async {
print("Dialog shown");
await showModalBottomSheet(
context: context,
backgroundColor: Colors.transparent,
builder: (context) => Container(
height: 270,
child: OTPBottomSheet(controller: _otpController),
),
);
return _otpController.text;
}
// >>>>>>>>>>>>> On Complete
final PhoneVerificationCompleted verificationComplete =
(AuthCredential authCred) async {
print(" I N S I D E C O M P L E T E ");
newUserResult = await signInWithPhoneNumber(authCred);
completer.complete(newUserResult);
};
// >>>>>>>>>>>>> On Timeout
final PhoneCodeAutoRetrievalTimeout autoRetrieve = (String verID) {
print("\n2. Auto retrieval time out");
completer.complete(newUserResult);
};
// >>>>>>>>>>>>> On manual code verification
final PhoneCodeSent smsCodeSent =
(String verID, [int forceCodeResend]) async {
print(" I N S I D E C O D E S E N T");
var OTPDialogResult = await getOTPresult();
if (OTPDialogResult != null) {
AuthCredential authCred = PhoneAuthProvider.credential(
verificationId: verID, smsCode: OTPDialogResult);
newUserResult = AuthService().signInWithPhoneNumber(authCred);
if (!completer.isCompleted) {
completer.complete(newUserResult);
}
}
};
// >>>>>>>>>>>>> On Ver failed
final PhoneVerificationFailed verificationFailed =
(Exception authException) {
completer.complete(newUserResult);
};
await FirebaseAuth.instance
.verifyPhoneNumber(
phoneNumber: phoneNo,
timeout: Duration(seconds: 50),
verificationCompleted: verificationComplete,
verificationFailed: verificationFailed,
codeSent: smsCodeSent,
codeAutoRetrievalTimeout: autoRetrieve,
).catchError((error) {
print(error.toString());
});
print("New user result at the end before await: " + newUserResult.toString());
newUserResult = await completer.future;
print("New user result at the end after await: " + newUserResult.toString());
return newUserResult;
}
signInWithPhoneNumber function
Future signInWithPhoneNumber(AuthCredential authCreds) async {
try {
UserCredential result = await FirebaseAuth.instance.signInWithCredential(authCreds);
User customUser = result.user;
return _userFormFirebaseUser(customUser).getuid;
}
CustData _userFormFirebaseUser(User user) {
print("----> Inside _userFormFirebaseUser and user ID: " + user.uid);
return user != null
? CustData(
custId: user.uid,
)
: null;
}
// --- CustData model class
class CustData {
String custId;
String custName;
String custPhNo;
String custContactNO;
DateTime custDateOfBirth;
Map<String, dynamic> address;
String cartID;
CustData({
this.custId,
this.custName,
this.custPhNo,
this.custDateOfBirth,
this.address,
this.cartID,
this.custContactNO,
});
CustData.initial() : custId = '';
String get getuid => this.custId;
}
Already making the whole procedure slow...
Try this Auth Version and it will work well for you :)
implementation com.google.firebase:firebase-auth:19.3.1
The reason behind this is explained in the official documentation https://firebase.google.com/docs/auth/android/phone-auth#enable-app-verification
reCAPTCHA verification: In the event that SafetyNet cannot be used, such as when the user does not have Google Play Services support, or when testing your app on an emulator, Firebase Authentication uses a reCAPTCHA verification to complete the phone sign-in flow. The reCAPTCHA challenge can often be completed without the user having to solve anything. Please note that this flow requires that a SHA-1 is associated with your application.
To solve it you have to :
In the Google Cloud Console, enable the Android DeviceCheck API for your project. The default Firebase API Key will be used, and needs to be allowed to access the DeviceCheck API.
If you haven't yet specified your app's SHA-256 fingerprint, do so from the Settings Page of the Firebase console. Refer to Authenticating Your Client for details on how to get your app's SHA-256 fingerprint.

How to check if phone number is already registered in firebase authentication using flutter

So i am making a simple sign up and login screens in flutter application that uses phone authentication of firebase. For sign up im able to register new user, as the user provides his phone number and gets OTP. But for login i wanna check if the entered number is already registered. If so he gets otp and logs in or if not registered then asks to sign up first.
Firebase admin SDK supports this. Here's how to set up firebase admin (documentation). After you set up admin, you can use cloud_functions package to call APIs from the firebase admin SDK and the API we'll be using is one that allows us to get a user by phone number (documentation). If the API response is a user record, we know a phone exists.
In this example, I'm using node.js. In functions/index.js:
exports.checkIfPhoneExists = functions.https.onCall((data, context) => {
const phone = data.phone
return admin.auth().getUserByPhoneNumber(phone)
.then(function(userRecord){
return true;
})
.catch(function(error) {
return false;
});
});
In your dart code:
final HttpsCallable callable = CloudFunctions.instance.getHttpsCallable(functionName: 'checkIfPhoneExists');
dynamic resp = await callable.call({'phone': _phone});
if (resp.data) {
// user exists
}
Once the OTP is sent to the user you can verify if the user is a new user or an existing one in verify OTP function
verifyOtp(String input, context) async {
String retVal = "error";
OurUser _user = OurUser();
print(input);
final AuthCredential credential = PhoneAuthProvider.credential(
verificationId: _verificationId, smsCode: input);
try {
// await _auth.signInWithCredential(credential);
UserCredential _authResult = await _auth.signInWithCredential(credential);
// Here i have to save the details of the user in the database
if (_authResult.additionalUserInfo.isNewUser) {
currentUser.uid = _authResult.user.uid;
currentUser.phone = _inputText;
currentUser.type = "Customer";
retVal = await OurDatabase().createUser(currentUser);
} else {
// get the information of the user from the database this already exists
currentUser = await OurDatabase().getUserInfo(_authResult.user.uid);
if(currentUser!= null) {
Navigator.pushNamedAndRemoveUntil(
context, "/homescreen", (route) => false);
}
}
print("End of the await");
// when signup with the otp
if (retVal == "success") {
print("why not inside this mane");
Navigator.pushNamedAndRemoveUntil(
context, "/homescreen", (route) => false);
}
saveAllData();
} catch (e) {
print(e);
print("Something went wrong");
//prin
}
}
Now this is when you want to verify OTP from the user and after the top is verified you can know if the user was indeed a new user or an old one but what if you wanted to know that beforehand then the best possible solution would be to create a new collection in the firestore that would have only one document(so you are charged only for one document read) that would just contain all the numbers of the users that are registered within your application,
I used a simple straight forward way and it worked just fine.
First, add the mobile number to the firebase database in a separate node when the user creates the account.
await dbref.child("RegisteredNumbers").push().set({
"phoneNo": FirebaseAuth.instance.currentUser!.phoneNumber,
});
whenever a user tries to log in or signup check in this node if the provided number is available in It or not.
Future<bool> checkNumberIsRegistered({required String number}) async {
bool isNumberRegistered = false;
try {
await dbref.child("RegisteredNumbers").once().then((data) {
for (var i in data.snapshot.children) {
String data = i.child("phoneNo").value.toString();
if (number == data) {
isNumberRegistered = true;
return isNumberRegistered;
} else {
isNumberRegistered = false;
}
}
});
return isNumberRegistered;
} catch (e) {
return false;
}
}
Hope it helps

How to Sign In with Twitter using Firebase_Auth with Flutter

How should one program a Sign In with Twitter feature using firebase_auth and Flutter?
I see a few examples using flutter_twitter_login or flutter_twitter, however they use a now Deprecated API and folks complain about Apple Store Rejection.
Firebase Auth offers a TwitterAuthProvider, but the following code remains incomplete:
final AuthCredential credential = TwitterAuthProvider.getCredential(
authToken: twitterAccessToken,
authTokenSecret: twitterAccessTokenSecret,
);
final AuthResult result = await auth.signInWithCredential(credential);
I was able to solve this using 3 resources:
The Flutter Facebook Sign In (with Firebase) in 2020 article
The Log in with Twitter guide
The Dart OAuth1 library
Ultimately, I was able to completely remove the flutter_twitter package, yet still support Sign in with Twitter.
Similar to the CustomWebView outlined in the Facebook solution, I created a TwitterLoginScreen like:
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:flutter_webview_plugin/flutter_webview_plugin.dart';
import 'package:oauth1/oauth1.dart';
/// Twitter Login Screen.
/// See [Log in with Twitter](https://developer.twitter.com/en/docs/basics/authentication/guides/log-in-with-twitter).
class TwitterLoginScreen extends StatefulWidget {
final twitterPlatform = Platform(
'https://api.twitter.com/oauth/request_token', // temporary credentials request
'https://api.twitter.com/oauth/authorize', // resource owner authorization
'https://api.twitter.com/oauth/access_token', // token credentials request
SignatureMethods.hmacSha1, // signature method
);
final ClientCredentials clientCredentials;
final String oauthCallbackHandler;
TwitterLoginScreen({
#required final String consumerKey,
#required final String consumerSecret,
#required this.oauthCallbackHandler,
}) : clientCredentials = ClientCredentials(consumerKey, consumerSecret);
#override
_TwitterLoginScreenState createState() => _TwitterLoginScreenState();
}
class _TwitterLoginScreenState extends State<TwitterLoginScreen> {
final flutterWebviewPlugin = FlutterWebviewPlugin();
Authorization _oauth;
#override
void initState() {
super.initState();
// Initialize Twitter OAuth
_oauth = Authorization(widget.clientCredentials, widget.twitterPlatform);
flutterWebviewPlugin.onUrlChanged.listen((url) {
// Look for Step 2 callback so that we can move to Step 3.
if (url.startsWith(widget.oauthCallbackHandler)) {
final queryParameters = Uri.parse(url).queryParameters;
final oauthToken = queryParameters['oauth_token'];
final oauthVerifier = queryParameters['oauth_verifier'];
if (null != oauthToken && null != oauthVerifier) {
_twitterLogInFinish(oauthToken, oauthVerifier);
}
}
});
_twitterLogInStart();
}
#override
void dispose() {
flutterWebviewPlugin.dispose();
super.dispose();
}
Future<void> _twitterLogInStart() async {
assert(null != _oauth);
// Step 1 - Request Token
final requestTokenResponse =
await _oauth.requestTemporaryCredentials(widget.oauthCallbackHandler);
// Step 2 - Redirect to Authorization Page
final authorizationPage = _oauth.getResourceOwnerAuthorizationURI(
requestTokenResponse.credentials.token);
flutterWebviewPlugin.launch(authorizationPage);
}
Future<void> _twitterLogInFinish(
String oauthToken, String oauthVerifier) async {
// Step 3 - Request Access Token
final tokenCredentialsResponse = await _oauth.requestTokenCredentials(
Credentials(oauthToken, ''), oauthVerifier);
final result = TwitterAuthProvider.getCredential(
authToken: tokenCredentialsResponse.credentials.token,
authTokenSecret: tokenCredentialsResponse.credentials.tokenSecret,
);
Navigator.pop(context, result);
}
#override
Widget build(BuildContext context) {
return WebviewScaffold(
appBar: AppBar(title: Text("Twitter Login")),
url: "https://twitter.com",
);
}
}
Then, the AuthCredential result from this screen can be passed to FirebaseAuth.signInWithCredential.
To sign in with Twitter do the following:
Future<FirebaseUser> loginWithTwitter() async {
var twitterLogin = new TwitterLogin(
consumerKey: 'key',
consumerSecret: 'secretkey',
);
final TwitterLoginResult result = await twitterLogin.authorize();
switch (result.status) {
case TwitterLoginStatus.loggedIn:
var session=result.session;
final AuthCredential credential= TwitterAuthProvider.getCredential(
authToken: session.token,
authTokenSecret: session.secret
);
FirebaseUser firebaseUser=(await firebaseAuth.signInWithCredential(credential)).user;
print("twitter sign in"+firebaseUser.toString());
break;
case TwitterLoginStatus.cancelledByUser:
break;
case TwitterLoginStatus.error:
break;
}
Use twitterlogin and pass the consumer key and consumer secret key, then use the method getCredential() and signInWithCredential to log in.
They have shared a common sample in the home page itself, only the 'sign in provider' changes, rest is same for all (google, fb and twitter). the result has a user property which will return the user details, check with the below code
final AuthCredential credential = TwitterAuthProvider.getCredential(
authToken: twitterAccessToken,
authTokenSecret: twitterAccessTokenSecret,
);
final AuthResult result = await auth.signInWithCredential(credential);
final FirebaseUser user = result.user;
print("signed in " + user.displayName);
This worked for me. (Referred from https://firebase.flutter.dev/docs/auth/social#twitter)
import 'package:twitter_login/twitter_login.dart';
Future<UserCredential> signInWithTwitter() async {
// Create a TwitterLogin instance
final twitterLogin = new TwitterLogin(
apiKey: '<your consumer key>',
apiSecretKey:' <your consumer secret>',
redirectURI: '<your_scheme>://'
);
// Trigger the sign-in flow
final authResult = await twitterLogin.login();
// Create a credential from the access token
final twitterAuthCredential = TwitterAuthProvider.credential(
accessToken: authResult.authToken!,
secret: authResult.authTokenSecret!,
);
// Once signed in, return the UserCredential
return await FirebaseAuth.instance.signInWithCredential(twitterAuthCredential);
}
This wasn't working initially for me.
Things I had to change in order for this to work.
Request for Elevated Permissions in Twitter Developer Portal.
Create a custom scheme (callback URL) and configure the ios/android files accordingly. As given in (https://pub.dev/packages/twitter_login).
Also configure this callback URL in the Twitter dev portal.

How do I update a FirebaseUser's phone number in firebase_auth?

In my Flutter app I use Firebase's phone number authentication as my main form of authentication. After authenticating, I create a user in my users collection with these details:
{
phoneNumber: FirebaseAuth.instance.currentUser().phoneNumber,
displayName: 'Comes from user textbox',
...
}
But say one day a user want's to change their phone number. How do I do this? Because I cannot simply change the user's phone number in the document, because the phone number needs to be authenticated. And after authentication, the user gets a new authUID. Which should then be a new user?
Could someone explain the logic behind a user that wants to keep their profile details but change their number.
In order to achieve this, you can use User.updatePhoneNumber. This allows you to update the phone number of a user.
You would use it in the same manner that you also authenticated with phone number in the first place (using signInWithCredential), i.e. you retrieve a credential using FirebaseAuth.verifyPhoneNumber and pass the credential that you get from either verificationCompleted or your user when they enter the SMS code they received. I will only sketch out what this would look like as I assume that you know how to perform this task:
FirebaseAuth.instance.verifyPhoneNumber(
phoneNumber: phoneNumber,
timeout: const Duration(minutes: 2),
verificationCompleted: (credential) async {
await (await FirebaseAuth.instance.currentUser()).updatePhoneNumber(credential);
// either this occurs or the user needs to manually enter the SMS code
},
verificationFailed: null,
codeSent: (verificationId, [forceResendingToken]) async {
String smsCode;
// get the SMS code from the user somehow (probably using a text field)
final AuthCredential credential =
PhoneAuthProvider.getCredential(verificationId: verificationId, smsCode: smsCode);
await (await FirebaseAuth.instance.currentUser()).updatePhoneNumber(credential);
},
codeAutoRetrievalTimeout: null);
When updatePhoneNumber is called, you probably also want to update your database document. Alternatively, you could listen to onAuthStateChanged and update your document this way.
async function save(phone: string, e) {
e.preventDefault();
const { currentUser:fuser } = firebase.auth();
if(fuser && fuser.phoneNumber !== phone) {
try {
const verifier = new firebase.auth.RecaptchaVerifier('recaptcha-container', {
callback: (response) => console.log('callback', response),
size: 'invisible',
});
const phoneProvider = new firebase.auth.PhoneAuthProvider();
const id = await phoneProvider.verifyPhoneNumber(phone, verifier);
const code = window.prompt('Bitte zugeschickten Code eingeben');
const cred = firebase.auth.PhoneAuthProvider.credential(id, code);
await fuser.updatePhoneNumber(cred);
console.log('phone number changed', id, cred, fuser);
setSuccess(true);
} catch(e) {
console.error(e);
}
}
}

How to use timer in flutter app for changing data?

I am using shared preferences to store the token, email, username and other user details when a user logs in using firebase authentication. The firebase token expires in every one hour so I need to refresh the token on the basis of when the user has returned to the app which I am doing in getCurrentUser() function below. I want to know that if a user has logged in my app, used it for 5 minutes or so and then close the application, will that timer function would still be listening and call the function after the timeout or not?
If it doesn't do so then How can I achieve checking this?
void checkTokenValidity(int time) {
Timer(Duration(seconds: time), () async {
print('token timed out');
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString('token', 'expired');
prefs.remove("currentUser");
});
}
Future<String> getCurrentUser() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
final String currentToken = prefs.getString('token');
final String cuser = prefs.getString('currentUser');
print("current: $cuser");
if (cuser != null && currentToken != 'expired') {
print('signed in and $currentToken');
SharedPreferences prefs = await SharedPreferences.getInstance();
String token = prefs.getString('token');
String uid = prefs.getString('userId');
String email = prefs.getString('userEmail');
String photo = prefs.getString('photo');
_authenticatedUser =
User(email: email, id: uid, token: token, photo: photo);
return 'success';
} else if (currentToken == 'expired') {
print('token is expired');
final FirebaseUser user = await FirebaseAuth.instance.signInAnonymously();
var token = await user.getIdToken();
prefs.setString('token', token);
String uid = prefs.getString('userId');
String email = prefs.getString('userEmail');
String photo = prefs.getString('photo');
_authenticatedUser =
User(id: uid, email: email, token: token, photo: photo);
checkTokenValidity(3600);
return 'token';
} else {
print('user is null');
return null;
}
}
In my authentication function which is not here, I have called checkTokenValidity(3600) just after the user successfully logs in.
I have also tried using FirebaseUser user = await FirebaseAuth.instance.currentUser(); but that also didn't solve the problem.
You went the wrong way. The right way is to add error handler on 401 (Unauthorized) error and handle it by refreshing token and retrying the same query.

Resources