Firebase Authenticate

Getting Started

We're going to augment our TodoMVC app that we tested in Part 1 to add basic Firebase authentication using Google Sign In. I'm going to be directing you to visit some of the Google code labs for the Firebase set up, so there will be a little jumping back and forth.

Download

Visit the GitHub repository to clone the source. This is highly recommended so you can play with the app. The final source code for this part is tagged as firebase_auth.

After cloning, you can checkout the code with git checkout firebase_auth.

NOTE: the source has only been tested to work on Android devices.

Add Firebase to your Project

Follow the steps from this portion of Google's code lab to configure your project to use Firebase. At the final step ("Add plugins to Friendlychat"), only the following plugins need to be added to pubspec.yaml:

  • google_sign_in
  • firebase_auth

https://codelabs.developers.google.com/codelabs/flutter-firebase/#4

Before continuing, make sure you have used your IDE to do a packages get, or that you have run flutter packages get on the command line.

Configuring Google Sign In

Next, to configure Google sign in for your app, follow the steps until you hit the "Sign in to Google" step: https://codelabs.developers.google.com/codelabs/flutter-firebase/#5

Test Compile

Without changing anything other than this, let's run the application to make sure everything is configured correctly.

If you get an error on Android regarding firebase_auth, you may need to upgrade your Gradle version.

Once everything is configured and running, we can now get our app to authenticate the user.

Adding Google Authentication

Firstly, we need to add the new imports to our App's library.

src/todo_app.dart
import 'dart:async';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';

We need async for our Future handling. The other two are the plugins we just configured above with the code labs.

Helper Utility

We're going to add a helper class to manage the code for signing in and out.

src/util/authentication.dart
part of todomvc;

final FirebaseAuth _auth = FirebaseAuth.instance;
final GoogleSignIn _googleSignIn = new GoogleSignIn();

Future<FirebaseUser> signInWithGoogle() async {
  // Attempt to get the currently authenticated user
  GoogleSignInAccount currentUser = _googleSignIn.currentUser;
  if (currentUser == null) {
    // Attempt to sign in without user interaction
    currentUser = await _googleSignIn.signInSilently();
  }
  if (currentUser == null) {
    // Force the user to interactively sign in
    currentUser = await _googleSignIn.signIn();
  }

  final GoogleSignInAuthentication auth = await currentUser.authentication;

  // Authenticate with firebase
  final FirebaseUser user = await _auth.signInWithGoogle(
    idToken: auth.idToken,
    accessToken: auth.accessToken,
  );

  assert(user != null);
  assert(!user.isAnonymous);

  return user;
}

Future<Null> signOutWithGoogle() async {
  // Sign out with firebase
  await _auth.signOut();
  // Sign out with google
  await _googleSignIn.signOut();
}

signInWithGoogle is where all the magic happens. We first authenticate the user with Google, then use the currentUser to generate a new OAuth signin token. We then pass that token information to Firebase to get (or create) our user.

signOutWithGoogle is pretty simple. We first sign out of Firebase, then from Google. It's important that we remember to sign out of Google as well, otherwise _googleSignIn.currentUser will remain valid and we will just be immediately logged back in as that user.

Finally, add this utility class to the App's library:

src/todo_app.dart
part 'util/authentication.dart';

Splash Page

We're going to update the app to use a splash page to manage the sign in functionality, rather than trying to smash it all into the Todo List page. We'll first create the page, then integrate it with the rest of our app.

src/pages/splash_page.dart
part of todomvc;

class SplashPage extends StatefulWidget {
  
  State createState() => new _SplashPageState();
}

class _SplashPageState extends State<SplashPage> {
  
  void initState() {
    super.initState();

    // Listen for our auth event (on reload or start)
    // Go to our /todos page once logged in
    _auth.onAuthStateChanged
        .firstWhere((user) => user != null)
        .then((user) {
      Navigator.of(context).pushReplacementNamed('/todos');
    });

    // Give the navigation animations, etc, some time to finish
    new Future.delayed(new Duration(seconds: 1))
        .then((_) => signInWithGoogle());
  }

  
  Widget build(BuildContext context) {
    return new Scaffold(
      body: new Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          new Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              new CircularProgressIndicator(),
              new SizedBox(width: 20.0),
              new Text("Please wait..."),
            ],
          ),
        ],
      ),
    );
  }
}

The magic in our splash page happens in initState. First, we subscribe to the onAuthStateChanged Stream and wait until we get a non-null user (meaning we've logged in). Once we have that user, we navigate to our TodoList page that we are going to set up as the named route /todos. That page will be able to read the user straight from _auth, so no additional context is needed.

For our next step, we need to update our home to the splash page and create the new route for our TodoList. Inside our MaterialApp, change the following:

lib/src/todo_app.dart
      home: new SplashPage(), // This used to be new TodoList()
      routes: <String, WidgetBuilder>{
        '/todos': (BuildContext context) => new TodoList(),
      },

The only part left for our authentication is logging out. We're going to add this as a menu item in a Drawer on the TodoList page. Add the following property to its Scaffold:

src/pages/todo_list.dart
      drawer: new Drawer(
        child: new ListView(
          primary: false,
          children: <Widget>[
            new DrawerHeader(
              child: new Center(
                child: new Text(
                  "Todo MVC",
                  style: Theme.of(context).textTheme.title,
                ),
              ),
            ),
            new ListTile(
              title: new Text('Logout', textAlign: TextAlign.right),
              trailing: new Icon(Icons.exit_to_app),
              onTap: () async {
                await signOutWithGoogle();
                Navigator.of(context).pushReplacementNamed('/');
              },
            ),
          ],
        ),
      ),

We now have a drawer and a "Logout" button. When the button is clicked, we call our signOutWithGoogle method from our Authentication Utility, then we navigate back to the splash page.

This has the side effect of attempting to sign user right back in, but they can pick a different google account after signing out, if they choose. Additionally, if we wanted to provide multiple signin methods, this would give them that option.

Our app now has access to a user associated with a google account. In our next part, we'll move our storage out of the state and into Firebase.

Brian Armstrong

Brian Armstrong

I'm Brian Armstrong, a SaaS developer with 15+ years programming experience. I am a Flutter evangelist and React.js enthusiast.