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.
Related
I want to integrate my app with Calendar API from Google. And in order to use it, I have to have an AuthClient (which is obtained from _googleSignIn.authenticatedClient();). The problem is, my GoogleSignIn().currentUser always return null and I don't know why. I already use Firebase Auth and Google Sign In.
This is my signInWithGoogle method:
Future signInWithGoogle() async {
try {
await GoogleSignIn().disconnect();
await FirebaseAuth.instance.signOut();
} catch (e) {
print(e.toString());
}
// Trigger the authentication flow
final GoogleSignInAccount? googleUser = await GoogleSignIn(scopes: [CalendarApi.calendarScope]).signIn();
// Obtain the auth details from the request
final GoogleSignInAuthentication googleAuth =
await googleUser!.authentication;
// Create a new credential
final credential = GoogleAuthProvider.credential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
// Once signed in, return the UserCredential
UserCredential result =
await FirebaseAuth.instance.signInWithCredential(credential);
User user = result.user!;
// note: this line always return null and I don't know why
print('current user auth ${GoogleSignIn().currentUser.toString()}');
return _userFromFirebaseUser(user);
}
Did I do something wrong in my code? Any help will be appreciated, thank you!
I also had the issue of GoogleSignIn().currentUser always being null but managed to (finally!) fix it by only initialising GoogleSignIn() once.
For those who want more details: I did this by creating a class called AuthManager that handles everything authentication-related, and making GoogleSignIn one of the parameters required to initialise it (since I'm using Firebase, this was the other parameter):
class AuthManager {
final FirebaseAuth _auth;
final GoogleSignIn _googleSignIn;
AuthManager(this._auth, this._googleSignIn);
Future signInWithGoogle() async {
final GoogleSignInAccount? googleUser = await _googleSignIn.signIn();
// etc....
}
GoogleSignInAccount? get googleAccount {
return _googleSignIn.currentUser;
}
}
And I initiaised by AuthManager class ONCE at the top of my app in a Provider, meaning that I can access it anywhere in my app.
In main.dart:
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
// To use AuthManager throughout app without initialising it each time
Provider<AuthManager>(
create: (_) => AuthManager(
FirebaseAuth.instance,
GoogleSignIn(scopes:
// Put whatever scopes you need here
),
),
),
// etc...
(Note: I used MultiProvider as I had other things I wanted to put, but if you only have one, you can obviously just go straight to Provider).
Now I can successfully get the current google user by getting googleAccount through my AuthManager class.
I'm having some problems with google sign in. It works - it signs me in but the screen just kind of flashes and the sign in flow where you'd choose your account never appears, then it just signs me in with my main google account.
Not sure what I'm doing wrong.
Its not normal behavior. I think normally a little dialog pops up and you get the chance to choose what google account you want to sign in with. This is the part that is not happening. I even tried on my phone, with a never installed before apk and never saw that dialog but it signed me with the default account.
Any help greatly appreciated:
general_providers.dart
import 'package:firebase_auth/firebase_auth.dart';
import 'package:riverpod/riverpod.dart';
final firebaseAuthProvider =
Provider<FirebaseAuth>((ref) => FirebaseAuth.instance);
final authStateChangesProvider = StreamProvider<User?>(
(ref) => ref.watch(firebaseAuthProvider).authStateChanges());
auth_widget.dart. -> this one is meant to just display the home page or login page depending on auth state:
class AuthWidget extends ConsumerWidget {
const AuthWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context, WidgetRef ref) {
final authStateChanges = ref.watch(authStateChangesProvider);
return authStateChanges.when(
data: (user) => user != null ? const HomePage() : LoginPage(),
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, __) => Scaffold(
body: Center(
child: Text(error.toString()),
),
),
);
}
}
firebase_auth_service.dart
final authServiceProvider =
Provider<FirebaseAuthService>((ref) => FirebaseAuthService(ref.read));
class FirebaseAuthService {
FirebaseAuthService(this._read);
final Reader _read;
//TODO add in try / catch with firebase exeptions
Future<void> signInAnonymously() async {
await _read(firebaseAuthProvider).signInAnonymously();
}
User? getCurrentUser() => _read(firebaseAuthProvider).currentUser;
Future<void> signOut() async {
await _read(firebaseAuthProvider).signOut();
}
Future<UserCredential> signInWithGoogle() async {
try {
// await _googleSignIn.signIn();
final GoogleSignInAccount? googleUser = await GoogleSignIn().signIn();
// Obtain the auth details from the request
final GoogleSignInAuthentication? googleAuth =
await googleUser?.authentication;
// Create a new credential
final credential = GoogleAuthProvider.credential(
accessToken: googleAuth?.accessToken,
idToken: googleAuth?.idToken,
);
return await _read(firebaseAuthProvider).signInWithCredential(credential);
} catch (error) {
throw FirebaseAuthException(code: error.toString());
}
}
}
The signInWithGoogle method above is pretty much copy paste from the docs except that I link my firebaseAuthProvider in the final line to get that top level auth instance.
When I run this method from the signin page it does sign me in but just skips the normal flow.
Any ideas?
I think this is the expected behavior. You would need to log out before you get to select account again.
I was looking through the Internet for a answer to my question, however, I was not able to find one. Therefore I am reaching out to you guys:
My problem: When I login with Facebook through Firebase (using flutter_facebook_login plugin) I can retrieve every data perfectly, except the photoURL. Whenever I try to show the profile picture in my UI, I end up with the standard profile picture of facebook, not the one the user is currently using. Any Ideas how I can change that? Thanks in advance!
Code I use to show the picture:
Image(image: NetworkImage(widget.user.photoUrl)
My self coded, Facebook Login Code:
class AuthService {
final _auth = FirebaseAuth.instance;
Stream<FirebaseUser> get currentUser => _auth.onAuthStateChanged;
Future<AuthResult> signInWithCredential(AuthCredential credential) =>
_auth.signInWithCredential(credential);
}
final facebookLogin = FacebookLogin();
final authService = AuthService();
Future<FirebaseUser> loginFacebook() async {
facebookLogin.loginBehavior = FacebookLoginBehavior.webViewOnly;
final result =
await facebookLogin.logInWithReadPermissions(['email', 'public_profile']);
switch (result.status) {
case FacebookLoginStatus.loggedIn:
final FacebookAccessToken accessToken = result.accessToken;
print('''
Logged in!
Token: ${accessToken.token}
User id: ${accessToken.userId}
Expires: ${accessToken.expires}
Permissions: ${accessToken.permissions}
Declined permissions: ${accessToken.declinedPermissions}
''');
break;
case FacebookLoginStatus.cancelledByUser:
print('Login cancelled by the user.');
break;
case FacebookLoginStatus.error:
print('Something went wrong with the login process.\n'
'Here\'s the error Facebook gave us: ${result.errorMessage}');
break;
}
// define Userdata
//final result = await facebookLogin.logInWithReadPermissions(['email']);
final FacebookAccessToken fbToken = result.accessToken;
final AuthCredential credential =
FacebookAuthProvider.getCredential(accessToken: fbToken.token);
final _result = await authService.signInWithCredential(credential);
print('${_result.user}');
return _result.user;
}
I am learning Firebase with Flutter.
Currently making an anonymous login option, here is the class I created:
class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
// sign in anonymously
Future signInAnonymous() async {
try{
// signs in as anon user
AuthResult signInResult = await _auth.signInAnonymously();
// retruns currently signed in user, else null
FirebaseUser userFromResult = signInResult.user;
return userFromResult; // HERE: if I add .uid, the id object is displayed
}catch(e){
print(e.toString());
return null;
}
}
}
In my login page after creating an instance and using the method, when I print the result I get FirebaseUser(Instance of 'PlatformUser') insted of the user information, here is the code:
onPressed: () async {
dynamic result = await _auth.signInAnonymous();
if(result == null){print('Error signing in.');}
else{
print('Signed in successfully');
print(result);
}
How can I access the user data?
UPDATE: If I change return userFromResult; to return userFromResult.uid; the id string is returned.
I still wonder, however, how to print the full object.
Your Result inside of the onpressed is a dynamic type cast, but it is a FirebaseUser inside.
// onPressed Callback
dynamic result = await _auth.signInAnonymous();
You can change your SignIn method with the right return type and use instead of dynamic the FirebaseUser.
Future<FirebaseUser> signInAnonymous() async {
// [...]
return userFromResult; // HERE: if I add .uid, the id object is displayed
}
onPressed: () async {
FirebaseUser result = await _auth.signInAnonymous();
print(result.uid); // should contain the id
// [...]
The difference is that in version 0.13.x the user data is available, but in the version used in this example the bersion used is 0.16.x.
Future<void> signUpWithGoogle() async {
try {
final GoogleSignInAccount googleUser = await _googleSignIn.signIn();
final GoogleSignInAuthentication googleAuth =
await googleUser.authentication;
final AuthCredential credential = GoogleAuthProvider.getCredential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
final FirebaseUser user =
(await _auth.signInWithCredential(credential)).user;
return user;
} catch (error) {
print(error);
}
}
I have done that above code to sign in with google and called the function of google sign button like this
signUpWithGoogle().then((value) => Navigator.of(context).push(MaterialPageRoute(builder: (_) {
return HomePage();
})));
But on first time when apk is installed normally the app is asking for choosing the google account
But after log out when i tap on the google SignIn button it is not asking in pop up menu to select the account.
And one more problem is their on clicking on the google signin button firsts it goes to the HomePage() then signIn is hapenning.
#override
void initState() {
super.initState();
getCurrentUser();
}
Future<void> getCurrentUser() async {
FirebaseUser user = await _auth.currentUser();
bool result = await facebookSignIn.isLoggedIn;
if (user != null && user.isEmailVerified == true) {
print("Email");
Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (_) {
return HomePage();
}));
}
}
Is this is the correct method to navigate to the HomePage() for those users who is signed in when app starts
Whenever user logout and sign in again with google account android is smart enough to provide google account directly to the app without giving sign in pop up
for your second problem plz ref this answer -:
Why might a function not get called in initState(){ super.initState()} in flutter, but work perfectly fine if called later in the same page?
first of all i am also new to flutter so this might not be the best solution however this is what i implement in my app for the authentication part
first i created a User class that contain an ID for the user
then i created a stream of user to my app so the app will always be provided with this value and what ever change happen to it in my services this is the code
final FirebaseAuth _auth = FirebaseAuth.instance;
Stream<FirebaseUser> user; // firebase user
User _userFromFireBaseUser(FirebaseUser user) {
return user != null ? User(uid: user.uid) : null;
}
// //auth change user stream
Stream<User> get userStream {
return _auth.onAuthStateChanged.map(_userFromFireBaseUser);
// or we can use .map((FirebaseUser user) => _userFromFireBaseUser(user) );
}
in my main widget
StreamProvider<User>.value(
lazy: false,
value: AuthService().userStream,
child: MaterialApp()//your main widget
then i created a statless wrapper class that read the stream value and according move to a page, in my app i used the wrapper to go to the sign in if the user is null else go to the profile page, in your application i guess it will go to the homepage
class ProfileWrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
final user = Provider.of<User>(context);
//print(user.uid);
if (user == null) {
print('no user');
return SignUpPage();
} else if (user != null) {
print('there is user');
print(user.uid);
return ProfilePage();
// print('there is user' + user.displayname);
// print('there is user' + user.photourl);
}
}
}
also you need to add the provider package in your pubspec.yaml file
provider: ^4.1.1
by doing this you don't need to handle any navigation between the home and the sign up, if your user is signed in you will automatically be navigated to the home page.
another solution if that is not what you are looking for, after the google sign function finishes check if the firebase user is not null, if there is a user navigate to your homepage