I have a Flutter project where I use Firebase Authentication. I'd like to test it locally. Here is how Firebase local emulator is started:
firebase emulators:start --project demo-test --only auth
Here is Firebase initialization from main.dart:
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: FirebaseOptions(
apiKey: 'any',
appId: 'any',
messagingSenderId: 'any',
projectId: 'demo-test',
));
//...
runApp(...);
}
Everything starts smoothly without errors. The app works. However when I try to sign up a new user:
firebase_auth.FirebaseAuth.createUserWithEmailAndPassword(email: email, password: password);
I receive an error:
[firebase_auth/unknown] com.google.firebase.FirebaseException: An internal error has occurred. [ API key not valid. Please pass a valid API key. ]
I suspect that I need to provide a proper API key for demo-test project but where can I find it? Or may be I can provide one when starting the emulator? I couldn't find answer in Google docs.
To clarify things. The app works fine when I use options of my real Firebase project. The problem comes only with a so-called Demo project.
Make sure to call useAuthEmulator in your code before using the authentication service. The Flutter code for that may be missing from the docs (I just filed an issue to get it added there), but the API to call can be found here.
I'm don't exactly recall how I addressed that cleartext issue last time I encountered it, but am quite sure it was by following some top search results including the android:usesCleartextTraffic="true" that you mention.
Related
I'm writing a flutter web app with Firebase.
The app is hosting on Firebase now and, to use Firebase Authentication service, I want Firebase.initializeApp() to be executed.
But there is a problem as mentioned on title.
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(const WebLoginApp());
}
When the app gets started, it shows only a white screen.
and also shows error log on DEBUG CONSOLE (case 1)
OR has break points with error log. (case 2)
Without await Firebase.initializeApp();, the app works well.
I've found another app I wrote for Firebase test has the same problem but it works well 3 weeks ago.
I've just updated Flutter and Chrome with latest version.
Could you give a hint or a solution?
It will be really appreciated.
You propably forgot the web installation for firebase, check this:
https://firebase.flutter.dev/docs/manual-installation/web
and you need initializeApp to this:
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
I'm following this guide, I'm having this code:
var acs = ActionCodeSettings(
url: 'https://example.com/auth/widget',
androidPackageName: 'com.example',
iOSBundleId: 'com.example',
handleCodeInApp: true,
androidInstallApp: true,
androidMinimumVersion: '12',
);
var emailAuth = 'john.doe#pm.me';
FirebaseAuth.instance
.sendSignInLinkToEmail(
email: emailAuth, actionCodeSettings: acs)
.catchError((onError, stackTrace) {})
.then((value) =>
print('Successfully sent email verification'));
Sending the email works, but when I click on the email, then…
in iOS it opens https://example.com/auth/widget - which is the fallback
in Android it shows a circular loader for about 1s and then it "falls down" and nothing happens
The incoming link handler
FirebaseDynamicLinks.instance.onLink.listen((dynamicLinkData) {
print('got dynamic link! $dynamicLinkData');
}).onError((error) {
print('error error!');
});
I configured dynamic links in Firebase to point to to.example.com. I also added a manual dynamic link to.example.com/test which opens my app (the got dynamic link! message shows up) - so all seems fine, the problem seems to lie in the link generation.
The link structure I get by email is:
https://to.example.com/?link=https://example-abcd.firebaseapp.com/__/auth/action?apiKey…%26continueUrl%3Dhttps://example.com/auth/widget%26lang%3Den&apn=com.example&amv=12&ibi=com.example&ifl=https://example-abcd.firebaseapp.com/__/auth/action?apiKey%3D…%26continueUrl%3Dhttps://example.com/auth/widget%26lang%3Den
After some more painful hours of debugging and reading documentation I finally found it out. Most of this is in the flutter documentation, but since the documentation has broken links and is a bit all over the place it was hard for me to catch it all!
Android
I needed to decrease the androidMinimumVersion from 12 to 1. Then my app opens and I can receive the dynamic link. No idea why. My android simulator is android version 13 but the app never opened.
Before decreasing the android version I also set the sha256 setting in firebase, using gradlew signingReport documented in this answer. Not sure though this was required.
iOS
I forgot to do all the steps documented in receiving links on iOS section, namely:
add the dynamic links domain into associated domains
add FirebaseDynamicLinksCustomDomains into Info.plist
Overall, I found that to get this feature working was really really hard for me as a Flutter beginner. But I guess a lot of the setup I can re-use as the dynamic links capability seems to be something which comes in handy in the future.
I use the latest version of sign_in_with_apple using the following code to allow signing in with Apple to my Flutter app on Android.
final credential = await SignInWithApple.getAppleIDCredential(
scopes: [
AppleIDAuthorizationScopes.email,
AppleIDAuthorizationScopes.fullName,
],
webAuthenticationOptions: WebAuthenticationOptions(
clientId: '***Service Identifier***',
redirectUri:
// For web your redirect URI needs to be the host of the "current page",
// while for Android you will be using the API server that redirects back into your app via a deep link
kIsWeb ? Uri.parse('https://${window.location.host}/') : Uri.parse('https://***Backend***/callback'),
),
nonce: nonce,
);
I have taken the code for the backend from the package README.md:
apple_router.post("/callback", (request, response) => {
console.log(">>> Apple callback received <<<");
console.log("Body:");
console.log(request.body);
console.log("Query:");
console.log(request.query);
console.log("Params:");
console.log(request.params);
const redirect = `intent://callback?${new URLSearchParams(
request.body
).toString()}#Intent;package=${process.env.ANDROID_PACKAGE_IDENTIFIER
};scheme=signinwithapple;end`;
console.log(`Redirecting to ${redirect}`);
response.redirect(307, redirect);
});
I have also configured everything at Apple with the correct domains, but on my backend, when I log into the app, only an empty request arrives:
>>> Apple callback received <<<
Body:
{}
Query:
{}
Params:
{}
Redirecting to intent://callback?#Intent;package=***Android package ID***;scheme=signinwithapple;end
Which is why it doesn't work properly in the app either:
E/flutter (27962): [ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: SignInWithAppleAuthorizationError(AuthorizationErrorCode.invalidResponse, parseAuthorizationCredentialAppleIDFromDeeplink: No `code` query parameter set))
I have checked everything several times and I no longer have any idea where this problem could come from. Does anyone have any ideas?
Using this sign_in_with_apple apple will provide you the email, fullName, etc. information only for the first authorizations.
So, you have to take few steps to apple authorization.
Flutter/client side code/mechanism
void loginSignUpWithApple() async {
SignInWithApple.getAppleIDCredential(
scopes: [
AppleIDAuthorizationScopes.email,
AppleIDAuthorizationScopes.fullName
],
).then((value) {
_socialLoginSignUp(value.authorizationCode, describeEnum(LoginMethod.APPLE));
}).catchError((e) {
showMessage("Something went wrong: $e", isError: true);
});
}
Backend side code/mechanism
Generate JWT using apple key id and private key
Get auth token from apple server by using generated jwt and clint sent authorizationCode then extract the IdToken from the auth token response
Decode the IdToken and get the IdTokenHeader
From the IdTokenHeader get the kId
Using the kId get the list of keys from apple server then extract the actual key
Using the actual key create apple key to public key
Using public key and IdToken get the claims then decode the claim to get the user email
After getting the user email you can store this email in your database or other place and you can trust this is mail as a valid apple mail.
You can see the backend implementation code which is we have implemented previously in java (spring boot).
Sample backend code for apple login (Try with VPN if link is not working)
I found the problem: if you are using express as your server, add
bodyParser.urlencoded({ extended: true })
as middleware for your callback handler and then request.body will not be empty.
I'm adding integration testing (using the integration_test package) to my app but I am running into a problem.
Let me explain. The first step when my app launch is authentication for which I have 3 options: firebase email link, firebase google sign in, and firebase facebook sign in.
What is blocking me is that all these sign in methods require actions outside of the main app dart code and thus are not accessible by flutter driver.
Am I missing something here? And if not how should that case be handled?
Cheers!
To make the test less flaky i would recommend to not rely on an internet connection or a third party (like Firebase or Google login).
I would advise to use a Mock for this. So when you try to login to your test you send a fake response, and in that way you can continue using the app.
The following article explains how to use a mock:
https://medium.com/stuart-engineering/mocking-integration-tests-with-flutter-af3b6ba846c7
You can use Patrol – it lets you interact with native system UI from within your Flutter integration tests. Example:
import 'package:flutter_test/flutter_test.dart';
import 'package:patrol/patrol.dart';
void main() {
patrolTest(
'signs in',
nativeAutomation: true,
(PatrolTester $) async {
await $.native.enterText(
Selector(textContains: 'Email'),
text: 'tester#awesomeapp.pl'),
);
await $.native.enterText(
Selector(textContains: 'Password'),
text: 'ny4ncat'),
);
await $.native.tap(Selector(text: 'Continue'));
// you should be signed in
});
}
You can add the fourth way of signing in - using username and password. Firebase should support this kind of very common situation, so you can do it within lines of code.
If you do not want the end users to login by password, you can simply disable this method in production build and only enable it in debug build.
Another way is to mock your authentication system. In other words, when doing testing, you have a button called "fake sign in", and your integration test driver just click that button.
I'm new to firebase messaging and flutter. According to the flutter firebase_messaging package docs, onTokenRefresh is fired when a new FCM token is generated. And according to Google's firebase docs there are two scenarios that triggers token generation:
When a new token is generated on initial app startup
Whenever an existing token is changed
Here is a simplified version of the main function of my application. After each execution, I delete the app from the emulator and the displayed token does indeed change. Despite this, onTokenRefresh is never fired and it should if my understanding of the documentation is correct.
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
FirebaseMessaging.instance.onTokenRefresh.listen((String token) {
print("New token: $token");
});
String token = await FirebaseMessaging.instance.getToken();
print("Token: $token");
//runApp(MyApp());
}
As I said, I'm new to flutter, dart and firebase messaging, is there something I'm fundamentally misunderstanding? Thanks.
So I think I figured it out. I noticed that sometimes, the onTokenRefresh does indeed fire. And I was wondering if it had something to do with how the flutter application is launched onto the emulator, in the sense that there is a race condition between when the token is generated and the listener attached.
To get the app to appear to start for the first time, I wiped the app data. Unfortunately this causes the flutter to automatically disconnect from the app which means I won't see the output of the print statement. So instead of trying to print when the token generation occurs, I assigned a value from the onTokenRefresh listener to a variable. I then updated a text widget with the value of the variable. And onTokenRefresh does indeed fire each time at start up if the app data has previously been wiped.