I developed an Agro App and I have enabled offline persistence because, after logging in, operators can spend 3-4 hours recording information offline until they return to the central point where there is internet.
The problem I have is that sometimes the application asks for credentials (login and password) again when they do not have internet, which means that they cannot record information offline or they have to go back to the central point to authenticate which is not efficient.
I understand that the Firebase auth token lasts up to an hour, how can I extend this time?
The way I am authenticating users using a provider is like following:
class UsuarioProvider {
final FirebaseAuth _firebaseAuth;
DatabaseReference db = FirebaseDatabase.instance.reference();
UsuarioProvider({FirebaseAuth firebaseAuth})
: _firebaseAuth = firebaseAuth ?? FirebaseAuth.instance;
Future <Map<String, dynamic>> signIn(String email, String password) async {
try {
UserCredential result = await FirebaseAuth.instance.signInWithEmailAndPassword(email: email, password: password);
User user = result.user;
final token = await user.getIdToken();
return {'ok' : true, 'token' : token, 'localId': user.uid, 'email' : user.email};
} catch (e) {
print(e);
return {'ok': false, 'code': '${e.code}', 'mensaje': '${e.message}' };
}
}
How can I increase the Firebase Token Time? or How can I prevent users from being prompted for credentials when they are offline?
UPDATE:
The way I'm using to call firebase is:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
final prefs = new PreferenciasUsuario();
await prefs.initPrefs();
final pushNotificationsProvider = new PushNotificationsProvider();
pushNotificationsProvider.initNotifications();
FirebaseDatabase database;
database = FirebaseDatabase.instance;
database.setPersistenceEnabled(true);
database.setPersistenceCacheSizeBytes(10000000); // 10MB de cache
runApp(MyApp());
}
Firebase Authentication uses two types of tokens: a refresh token that "never" expires, and a short-lived ID token that is valid for an hour but is auto-refreshed by the SDK. There is no way to expand the life-time of the ID tokens.
If you want to pass a value along to a client that they can use for a longer period of time, you can mint an authentication cookie, which can be valid for up to two weeks. Note that this is a sensitive operation, so it can only be performed in trusted environments (typically by using the Firebase Admin SDK). For a full walkthrough of the process, see the Firebase documentation on Managing Session Cookies.
Related
Actually I'm trying to get current user id after authentication but I don't know how to do it. While registering email/password authentication only stores email, password as well as uid. I tried to fetch that uid by calling following function after pressing login button but it return null. I am not able to get the uid of current user.
Calling a function after pressing login button:
final FirebaseAuth auth = FirebaseAuth.instance;
Future<void> inputData() async {
final User? user = await auth.currentUser;
final uid = user?.uid;
// here you write the codes to input the data into firestore
print("User id: ${uid}");
}
You can see in the console it prints null:
Most likely your inputData function runs before the user is signed in. To ensure your code can properly react to when the user is signed in or out, use an auth state listener as shown in the first snippet in the documentation on getting the current user.
Future<void> inputData() async {
FirebaseAuth.instance
.authStateChanges()
.listen((User? user) {
if (user != null) {
print(user.uid);
}
});
}
one reason why you might be getting null is you're not awaiting the getToken function, so make it a future and await it, I'm sure you'll get the token provided the user is authenticated.
i'm trying to show a list of data coming from the cloud firestore, and applied a filter to get this data by month. Everything works pretty fine unless once another user logs in, the data received afterwards is his data and not of the actual logged in user. Once I remove the app from the background it gets me data correctly at that time.
After some research, I realized that it has to do with cache data. I tried adding the following lines to my code:
Logout function:
await FirebaseFirestore.instance.clearPersistence();
await FirebaseFirestore.instance.disableNetwork();
Login function:
await FirebaseFirestore.instance.enableNetwork();
But nothing actually changes, is there anything I can edit/add to solve this?
Here is the code of full functions:
Future<void> loginWithEmailAndPassword(String email, String password) async {
await FirebaseFirestore.instance.enableNetwork();
UserCredential userCredential =
await auth.signInWithEmailAndPassword(email: email, password: password);
}
Future<void> logOut() async {
await FirebaseFirestore.instance.clearPersistence();
await FirebaseFirestore.instance.disableNetwork();
await auth.signOut();
}
}
Edited my code and now getting this error:
FirebaseException ([cloud_firestore/failed-precondition] Operation was rejected because the system is not in a state required for the operation's execution. If performing a query, ensure it has been indexed via the Firebase console.)
The Official Firebase docs say:
...
3.Get a Credential object for the new authentication provider:
// Google Sign-in
final credential = GoogleAuthProvider.credential(idToken: idToken);
// Email and password sign-in
final credential =
EmailAuthProvider.credential(email: emailAddress, password: password);
// Etc.
4.Pass the Credential object to the sign-in user's linkWithCredential() method:
try {
final userCredential = await FirebaseAuth.instance.currentUser
?.linkWithCredential(credential);
} on FirebaseAuthException catch (e) {
// ...
}
I have not found any way to get a credential (more specifically, an AuthCredential) for Apple, which is needed as an argument to the function:
FirebaseAuth.instance.currentUser.linkWithCredential(AuthCredential credential)
I have found ways to link Google and Facebook accounts to existing accounts on my firebase app by following the above described methods, just couldn't figure out a way to get that credential object for Apple. Here's what I tried:
For Google, there is:
final credential = GoogleAuthProvider.credential(...);
For Facebook, there is:
final credential = FacebookAuthProvider.credential(...);
I could not find this in any docs, I checked, and yes, for Apple there is a similar function:
String accessToken = ? //how do I get this???
final credential = AppleAuthProvider.credential(accessToken);
The problem is, how do I get this accessToken String, which is required as a parameter? I have searched everywhere, and couldn't find a solution.
The only possibility I see now to link an apple account to an existing account on my app, is to:
call the same code that I use for Signing in With Apple
and warn the user that he should use "Show My Email" and NOT "Hide My Email", so that this "Sign in With Apple" gets added to the existing firebase account (important: I link accounts that have the same email in my firebase auth settings).
Because if the user chooses "Hide My Email" instead, a new firebase account will be created with the randomly generated email address (for anonymity) from Apple.
I have used the "the_apple_sign_in" package before. In this package:
You can get the access token from the credantial as String.fromCharCodes(appleIdCredential.authorizationCode!).
You can get the id token from the credantial as String.fromCharCodes(appleIdCredential!.identityToken!).
Perhaps you can similarly get the Firebase credential. Also here you can find a sample code with this package:
final FirebaseAuth _auth = FirebaseAuth.instance;
Future<UserModel?> signInWithApple(
{List<Scope> scopes = const [Scope.email, Scope.fullName]}) async {
UserModel userModel;
// 1. perform the sign-in request
final result = await TheAppleSignIn.performRequests(
[AppleIdRequest(requestedScopes: scopes)]);
// 2. check the result
switch (result.status) {
case AuthorizationStatus.authorized:
final appleIdCredential = result.credential;
final oAuthProvider = OAuthProvider("apple.com");
final credential = oAuthProvider.credential(
idToken: String.fromCharCodes(appleIdCredential!.identityToken!),
accessToken:
String.fromCharCodes(appleIdCredential.authorizationCode!),
);
final authResult = await _auth.signInWithCredential(credential);
final firebaseUser = authResult.user;
firebaseUser!.updateDisplayName(
'${appleIdCredential.fullName!.givenName} ${appleIdCredential.fullName!.familyName}');
userModel = UserModel(
uid: firebaseUser.uid,
name: firebaseUser.displayName,
email: firebaseUser.email,
image: firebaseUser.photoURL,
idToken:
String.fromCharCodes(appleIdCredential.authorizationCode!));
return userModel;
case AuthorizationStatus.error:
return null;
case AuthorizationStatus.cancelled:
return null;
}
}
Is it possible to achive authentication with email and password in flutter without using firebase? I have searched around Stackoverflow and internet in general and found nothing about this.
I am creating a simple authentication class this is what I have done at the moment:
class User {
bool isAuthenticated = false;
late String userid;
late String username;
late String email;
late DateTime expireDate; // this variable is used to make the user re-authenticate when today is expireDate
User(bool isAuthenticated, String userid, String username, String email) {
this.isAuthenticated = isAuthenticated;
this.userid = userid;
this.username = username;
this.email = email;
this.expireDate = new DateTime.now().add(new Duration(days: 30));
}
}
class Authentication {
Future<User> signin(String email, String password) {}
void signup(String username, String email, String password) {}
}
EDIT #1: I know how to setup a cookie/token based authentication server I have my own repos on that topic: cookie authentication, token authentication but I don't know how to handle the tokens/cookies in flutter.
This answer is based of #edit1. Since you mentioned that you already know how to set up tokens on the server side you're half way done. Here's a few assumptions I'm making, you already know js/php and worked with JSON output, The database already has a column and table that keeps track of sessions and user_id.
Since you know how Cookies are built this should be relatively easy cause i built it around similar architecture. We has to use the local memory that app's provide access to. There are two packages in flutter that allow u to do this, you can use either:
shared_preferences package link
flutter_secure_storage package link
The main difference is if you want to store 'tokens' or data you want secure you would obviously use flutter_secure_storage. I'm going to use this for code example. And yes the data is saved even after the app is closed.
Setting up Tokens(flutter):
Setting up User Class
When using firebase we generally take for granted the user class that comes with flutter_auth but that is basically what we have to build. A user class with all the data u want to store and then a function called authenticate.
class AppUser{
final _storage = new FlutterSecureStorage();
//below class is mentioned in the next part
AuthApi api = new AuthApi();
//constructor
AppUser(){
//ur data;
};
Future<bool> authenticate(email, password) async {
//this is the api mentioned in next part
http.Response res = await api.login(email, password);
Map<String, dynamic> jsonRes = jsonDecode(res.body);
if (jsonRes["error"]) {
return false;
}
_setToken(jsonRes["token"]);
_setUID(jsonRes["user-id"].toString());
_setAuthState(true);
return true;
}
Future<void> _setToken(String val) async {
//how to write to safe_storage
await _storage.write(key: 'token', value: val);
}
Future<void> _setUID(String val) async {
await _storage.write(key: 'user_id', value: val);
}
//you can stream this or use it in a wrapper to help navigate
Future<bool> isAuthenticated() async {
bool authState = await _getAuthState();
return authState;
}
Future<void> _getAuthState() async {
//how to read from safe_storage u can use the same to read token later just replace 'state' with 'token'
String myState = (await _storage.read(key: 'state')).toString();
//returns boolean true or false
return myState.toLowerCase() == 'true';
}
Future<void> _setAuthState(bool liveAuthState) async {
await _storage.write(key: 'state', value: liveAuthState.toString());
}
}
and assuming ur going to authenticate on a button press so it would look like
onPressed(){
AuthUser user = new AuthUser();
if(user.authenticate(email, password)){
//if logged in. Prolly call Navigator.
}else{
//handle error
}
}
Setting up api calls
Oka so this is calling a Node express API, and the json output looks like
//if successful
{"status":200, "error": false, "token": "sha256token", "user-id": "uid"}
we need to create a class that will give us an output for making this call hence the AuthApi class
class AuthApi {
//this is the login api and it returns the above JSON
Future<http.Response> login(String email, String password){
return http.post(
Uri.parse(ip + '/api/auth/login'),
headers: <String, String>{
'Content-Type': 'application/json',
},
body: jsonEncode(<String, String>{
"email": email,
"password": password,
}),
);
}
}
Thank you for clarifying what u needed, it helped answer better.
You can use Nodejs & express to create your own API and MongoDB or any other DB to act as a persistent DB. I am attaching my github repo link which has minimum code required to setup a email/password auth in mongodb
Github
EDIT :
I have little to no idea about sessions but for tokens there are packages in pub.dev which lets you decode the tokens. jwt-decoder.
You can check the expiry time of the token using this package and for storing them you can use secure_storage
I had a look at your token authentication repo. I would suggest you to verify the token when you get them and not just blindly trust them.
Yes it is Totally possible to create Authentication without Firebase, but it becomes a-lot more difficult and there are multiple solutions.
What firebase provides:
Server space with no down time
Complete set of Api's including authentication with various methods
Strong security(built by google)
Ease of use and setup with great documentation
The reason I bring these up is cause the alternative ur looking for is very difficult for a programer who's relatively new and can feel like you are building multiple applications at a time. It's definitely a learning curve. Also I'm assuming u don't just want local authentication cause thats kinda pointless.
Creating ur own backend involves:
Setting up a server(usually ubuntu)(and either on a raspi or a host like amazon, digital ocean, etc)
Setting up a database with tables(mysql, sql, mongoDB)
Creating communication API's (php, Node.js)
So here's what i'd recommend for getting into backend dev,
use LAMP architecture : Linux, Apache, MySQL, PHP
Setting up Lamp isn't too hard heres a link i followed:
https://www.digitalocean.com/community/tutorials/how-to-install-linux-apache-mysql-php-lamp-stack-on-ubuntu-16-04
After u set up ur back end, then u have to create api calls from flutter.
The call (if u created an auth.php where people can login) would look something like:
http://ip:8080/auth.php?email="example#gmail.com"&pass="123456"
I understand why you feel like you didn't find solutions, i was there too but there are tons,LAMP is one of the more easier ones. If u are still interested i'd recommend checking out System Design courses.
I am working with flutter and I have a AuthenticationProvider. Whenever my user signs in with his phone I update his profile as well. But my problem is that auth users can't be queried. So I read that I should keep a separate user collection. Now my question is, is it possible to update a user document in my user collection whenever a user updates his auth profile? I would like to do this with cloud functions but I noticed that there is only a create and delete? So how can I do this?
This is what I currently have
Authentication Provider
Future<void> _verificationComplete(BuildContext context, AuthCredential authCredential, userInfo.UserInfo userInfo) async {
AuthResult authResult = await FirebaseAuth.instance.signInWithCredential(authCredential);
final userUpdateInfo = UserUpdateInfo();
userUpdateInfo.displayName = userInfo.name;
userUpdateInfo.photoUrl = userInfo.photoUrl;
await authResult.user.updateProfile(userUpdateInfo);
await authResult.user.reload();
user = UserModel.fromFirebase(authResult.user);
_status = AuthenticationStatus.authenticated;
notifyListeners();
}
Cloud function
export const onUserCreated = functions.region('europe-west1').auth.user().onCreate(async user => {
const privateUserData = {
activeGroup: '',
cloudMessagingToken: '',
}
const publicUserData = {
name: '',
photoUrl: '',
}
const promises = [];
promises.push(firestore.collection('users').doc(user.uid).collection('private').doc('data').set(privateUserData));
promises.push(firestore.collection('users').doc(user.uid).collection('public').doc('data').set(publicUserData));
return await Promise.all(promises);
});
There is no Cloud Functions trigger for when a user updates their Firebase Authentication profile. I'd highly recommend filing a feature request for that, as it's much missed.
For now, the closest you can get is with a Cloud Function that you call directly from the application code. The two options there are:
Have your application code call the Firebase Authentication API first, then when that completes, have it call your custom Cloud Function to update the database too.
Have your application code call the Cloud Function immediately, and then have the Cloud Function update both the user profile and the database.
I somehow often do the first one, but see more developers take the second approach. I think their approach is probable simpler, but I just haven't gotten around to it yet. :)