Flutter firebase auth security check - firebase

I'm new to flutter and i'm using firebase auth as my authentication to the application. I'm from a php background and do I need to check if the user is logged in every new page like we check state in php. I'm using this method to handle auth,
Widget handleCurrentScreen() {
return StreamBuilder<FirebaseUser>(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (BuildContext context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Scaffold(
appBar: AppBar(
title: Text("Splash"),
),
body: Text("Splash"),
);
} else {
if (snapshot.hasData) {
return HomePage(_auth,_googleSignIn);
}
return LoginScreen3();
}
}
);
}
My question is here once I'm in this page HomePage(), Do I need to verify auth as a good practice or to prevent from hackers?

Related

Not changing screen based on Authentication Status Flutter

I am trying to go to the home screen or stay on auth screen based on whether or not the user is successfully authenticated. Using Firebase authentication's authStateChanges and a stream builder.
I get no error codes and the console reads
D/FirebaseAuth(21665): Notifying id token listeners about user ( pG6pORODSGMi21fuaoql29hqXZp2 ).
D/FirebaseAuth(21665): Notifying auth state listeners about user ( pG6pORODSGMi21fuaoql29hqXZp2 ).
so the authentication is successful and when I hot restart the app it goes to the home screen and displayed info as it should.
Here is the code
FutureBuilder(
future: _initialization,
builder: (context, appsnapShot) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
routes: {
SignupScreen.routeName: (ctx) => SignupScreen(),
AuthScreen.routeName: (ctx) => AuthScreen(),
HomeScreen.routeName: (ctx) => HomeScreen(),
UserPhoneAdds.routeName: (ctx) => UserPhoneAdds(),
PhoneAddForm.routeName: (ctx) => PhoneAddForm(),
EditPhoneScreen.routeName: (ctx) => EditPhoneScreen(),
},
home: appsnapShot.connectionState != ConnectionState.done
? SplashScreen()
: StreamBuilder(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return SplashScreen();
}
if (snapshot.hasData) {
return HomeScreen();
} else {
return AuthScreen();
}
},
),
);
});
FYI-this is off of a course and in the app it came from this works correctly
Thank you for any help.
The authStateChanges doesn't return a snapshot but a User as you can see in the officaial documentation:
FirebaseAuth.instance
.authStateChanges()
.listen((User user) {
if (user == null) {
print('User is currently signed out!');
} else {
print('User is signed in!');
}
});
So you sould just check if User is null or not.

Flutter Streambuilder not working as expected with Firebase

Not sure I am thinking about this right. According to my knowledge the Streambuilder is supposed to log you out if the user has been deleted from the backend which is Firebase.
The steps of what I am doing as of now -
loading the app
Signing in to the app
Loading firebase and deleting the signed in user from the backend.
I believe doing this would log me out from the app as well. Is that right?
Here is my code -
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MaterialApp(
theme: ThemeData(
accentColor: Colors.orange,
primarySwatch: Colors.blue,
),
home: StreamBuilder(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
print(FirebaseAuth.instance.authStateChanges());
if (snapshot.connectionState == ConnectionState.active) {
var user = snapshot.data;
if (user == null) {
return Welcome();
}
return Conversations("Logged in");
}
)
));
}
Firebase Authentication uses a combination of long-lived and short-lived tokens to manage login sessions, and it may take up to an hour before the short-lived token needs to be refresh and the client detects that the client is signed out.
If you waited for less time than an hour, that is probably the reason your authStateChanges() didn't fire with a new value: the token is still valid, so the client's auth state hasn't changed yet.
If you want to learn how to revoke the user's tokens, I recommend reading the documentation on that. Fair warning though: it is quite a bit more involved than simply signing in and out on the client.
If your goal is to be able to lock out users instantly, there are probably other ways to do that. For example, when using Firebase's Realtime Database or Firestore, it is quite common to keep a list of "blocked UIDs" in the database, and check against that in the security rules of your database.
When logging out by using signOut(), the state got updated right away, but it might not be the case when you delete the user.
The change might take a while to be notified to the stream at the front end. You can read more on that here: Firebase user deleted but still logged in on device
Firebase Authentication tokens are valid for one hour and cached on the user's device. It is automatically refreshed by the client SDK. Deleting the account doesn't proactively reach out to all the user's devices to delete the token.
You can try on this mini sign-in app with the provided signOut() method:
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.indigo,
),
home: MyApp(),
);
}
}
class LandingPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamBuilder<User>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
User user = snapshot.data;
if (user == null) {
return Welcome();
}
return Conversations();
} else {
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
},
);
}
}
class Welcome extends StatelessWidget {
Future<void> _signInAnonymously() async {
try {
await FirebaseAuth.instance.signInAnonymously();
} catch (e) {
print(e);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Sign in')),
body: Center(
child: RaisedButton(
child: Text('Sign in anonymously'),
onPressed: _signInAnonymously,
),
),
);
}
}
class Conversations extends StatelessWidget {
Future<void> _signOut() async {
try {
await FirebaseAuth.instance.signOut();
} catch (e) {
print(e); // TODO: show dialog with error
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home Page'),
actions: <Widget>[
FlatButton(
child: Text(
'Logout',
style: TextStyle(
fontSize: 18.0,
color: Colors.white,
),
),
onPressed: _signOut,
),
],
),
);
}
}

How to use .currentUser method in flutter

i have some code:
getFavSalons(AsyncSnapshot<QuerySnapshot> snapshot) {
return snapshot.data.documents
.map((doc) => SalonBlock(
salonName: doc["salonName"],
location: doc["location"],
workTime: doc["workTime"],
rating: doc["rating"],
))
.toList();
}
and part of code where I building list:
StreamBuilder(
stream: Firestore.instance
.collection("customers")
.document("HAQaVqCPRfM7h6yf2liZlLlzuLu2")
.collection("favSalons")
.snapshots(),
builder:
(context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasData) {
return Container(
margin:
EdgeInsets.only(bottom: screenHeight * 0.33),
child: new ListView(
children: getFavSalons(snapshot),
),
);
}
return LoadingSalon();
}),
and here I use uid:
.document("HAQaVqCPRfM7h6yf2liZlLlzuLu2")
here I have to use currentUser instead of filling myself. How to do this?
The current user in you application can change at any moment. For example:
When the user starts the application, Firebase automatically restores their previous authentication state. But this requires it to call out to the server, so the user is briefly not signed in (currentUser is null) before it is signed in.
While the user is signed in, Firebase refreshes their authentication state every hour to ensure their sign-in is still valid (and for example their account hasn't been disabled). This means that their sign-in state can change even when you don't explicitly call the API.
For these reasons you can't simply call currentUser and expect it to remain valid. Instead you should attach an auth state change listener, which gives you a stream of authentication states.
In your code that builds the UI, you can use this stream of user data inside another stream builder. So you'll have two nested stream builders:
For the user authentication state.
For the database, based on the current user.
So something like (untested for now):
StreamBuilder(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, AsyncSnapshot<User> snapshot) {
if (snapshot.hasData) {
return StreamBuilder(
stream: Firestore.instance
.collection("customers")
.document(snapshot.data.uid)
.collection("favSalons")
.snapshots(),
builder:
(context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasData) {
return Container(
margin:
EdgeInsets.only(bottom: screenHeight * 0.33),
child: new ListView(
children: getFavSalons(snapshot),
),
);
}
return LoadingSalon();
}),
}
return Text("Loading user...");
}),
FirebaseUser is currently deprecated, you can get the CurrentUser like shown below;
FirebaseAuth.instance.currentUser;
If you want to know more about what arguments you can use with it check out their documentation;
https://firebase.flutter.dev/docs/auth/usage
Make sure you have firebase_auth imported to your class
Create instances of FirebaseAuth and User like so:
final auth = FirebaseAuth.instance;
User currentUser;
/// Function to get the currently logged in user
void getCurrentUser() {
currentUser = auth.currentUser;
if(currentUser) {
// User is signed in
} else {
// User is not signed in
}
}
You can call the getCurrentUser function in the initState of a Stateful Class to get the current as the Widget is loaded like so:
#override
void initState() {
getCurrentUser();
super.initState();
}
You can now change your previous code to this:
StreamBuilder(
stream: Firestore.instance
.collection("customers")
.document(currentUser.uid)
.collection("favSalons")
.snapshots(),
builder:
(context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasData && snapshot.connectionState == ConnectionState.active) {
return Container(
margin:
EdgeInsets.only(bottom: screenHeight * 0.33),
child: new ListView(
children: getFavSalons(snapshot),
),
);
}
return LoadingSalon();
}),
This should work for you now :)

Firebase auth user information stream

I have a profile page the shows the user his currently registered email and an edit icon that takes him to the page where he can edit the email.
When the user changes the email successfully, he is returned to the previous page that showed him the registered email.
I want the user to see the new email. But the user keeps seeing the old email until the page is reloaded.
So my first thought was to naturally replace the FutureBuilder with StreamBuilder.
I was not sure how to get a stream of the current user and not the future, so I used the only stream I know for FirebaseUser --> onAuthStateChanged
Widget buildEmail() {
return StreamBuilder(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (BuildContext context, AsyncSnapshot<FirebaseUser> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting)
return Center(
child: SpinKitRing(
color: Colors.white,
size: 30,
));
else if (snapshot.hasError)
return Center(child: Text(snapshot.error));
else {
// final email = snapshot.data.data['email'];
final email = snapshot.data.email;
return Padding(
padding: const EdgeInsets.only(left: 25.0),
child: Text('$email', style: TextStyle(color: Colors.black)),
);
}
},
);
}
This StreamBuilder behaves identically to the FutureBuilder I had before
Widget buildEmail() {
return FutureBuilder(
future: FirebaseAuth.instance.currentUser(),
builder: (BuildContext context, AsyncSnapshot<FirebaseUser> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting)
return Center(
child: SpinKitRing(
color: Colors.white,
size: 30,
));
else if (snapshot.hasError)
return Center(child: Text(snapshot.error));
else {
// final email = snapshot.data.data['email'];
final email = snapshot.data.email;
return Padding(
padding: const EdgeInsets.only(left: 25.0),
child: Text('$email', style: TextStyle(color: Colors.black)),
);
}
},
);
}
}
So I looked into the documentation of onAuthStateChanged and it states:
/// Receive [FirebaseUser] each time the user signIn or signOut
This lead me to check on my update email method and check if my user is being signed in again to the app.
static Future<void> updateEmail(String password, String newEmail, BuildContext context) async {
final user = await CurrentUser.getCurrentUser();
final ref = Firestore.instance.collection('users');
try {
AuthCredential credential = EmailAuthProvider.getCredential(email: user.email, password: password);
AuthResult result = await user.reauthenticateWithCredential(credential); // Sign in?
result.user.updateEmail(newEmail);
ref.document(user.uid).updateData({'email': newEmail});
Navigator.of(context).pop();
Fluttertoast.showToast(msg: 'Success!');
} on PlatformException catch (error) {
print(error);
changeEmailErrorDialog(context, error: error.message);
} catch (error) {
print(error);
changeEmailErrorDialog(context, error: error);
}
}
I think that I am signing in the user since this method prevents the ERROR_REQUIRES_RECENT_LOGIN exception
AuthResult result = await user.reauthenticateWithCredential(credential); // Sign in?
Am I doing something wrong?
How can I get a stream of the user?
The Firebase method for obtaining the current user returns a future
FirebaseAuth.instance.currentUser();
Shouldn't there be a stream version of this method?
There is a stream version of it;
FirebaseAuth.instance.userChanges(),
Example;
StreamBuilder<User>(
stream: FirebaseAuth.instance.userChanges(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
}
return Text(snapshot.data.displayName);
},
),

usernames in google auth flutter

I am facing the following problem: I am displaying the username of the google account of the user in a subtitle in a list tile, but the code that I use displays an error if the user is not logged in first, so how can I edit this code to display you are not logged in when the user is not logged in or is logged out. Also, how to display the username of the google account if the user is signed in or changed his account this is the code:
subtitle: new FutureBuilder<FirebaseUser>(
future: FirebaseAuth.instance.currentUser(),
builder: (BuildContext context,AsyncSnapshot<FirebaseUser> snapshot){
if (snapshot.connectionState == ConnectionState.waiting) {
return new Text(snapshot.data.displayName);
}
else {
return new Text('you are not logged in');
}
},
Fixing the problem
You can simply replace snapshot.connectionState == ConnectionState.waiting with snapshot.hasData, which is the equivalent of snpashot.data != null. This, however, will display 'you are not logged in' even when still waiting. I added a 'loading' Text for waiting:
builder: (BuildContext context, AsyncSnapshot<FirebaseUser> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Text('loading');
} else if (!snapshot.hasData) {
return Text('you are not logged in');
} else {
return Text(snapshot.data.displayName);
}
}
This works because currentUser() returns null if there is no current user.
A suggestion
You are currently using currentUser(), which does not update on authentication changes. You can use onAuthStateChanged, which is a stream that will update every time and always provide you with the latest user. For this, you will have to migrate to StreamBuilder:
subtitle: StreamBuilder(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (BuildContext context, AsyncSnapshot<FirebaseUser> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Text('loading');
} else if (!snapshot.hasData) {
return Text('you are not logged in');
} else {
return Text(snapshot.data.displayName);
}
},
)

Resources