Authenticating Users

Create a sign-in flow for users and use profile information to provide users with personalized functionality using Firebase Authentication.

This page and sample are part of an extended learning example of a simple blog application where users can upload posts. You should already be familiar with the Go programming language and basic web development. To start from the beginning, go to Building an App with Go.

Costs

There are no costs associated with running this tutorial. Running this sample app alone does not exceed your free quota.

Before you begin

If you have completed the Building an App with Go guide, skip this section. Otherwise, complete the following steps:

  1. Complete the tasks in Before you begin in Setting Up Your Project and Application.

  2. In this example, you add code to the gophers-4 sample, which is the final product from Storing Data.

    Download the gophers-4 sample and its dependencies to your local machine:

    go get -u -d github.com/GoogleCloudPlatform/golang-samples/appengine/gophers/gophers-4/...
    
  3. Change to the gophers-4 directory:

    cd $GOPATH/src/github.com/GoogleCloudPlatform/golang-samples/appengine/gophers/gophers-4
    

Structuring your application

This sample project has the following structure:

  • go-app/: Project root directory.

    • app.yaml: Configuration settings for your App Engine application.
    • main.go: Your application code.
    • index.html: HTML template to display your homepage.
    • static/: Directory to store your static files.
      • style.css: Stylesheet that formats the look of your HTML files.
      • gcp-gopher.svg: Gopher image.
      • index.js: Configures the Firebase Authentication user interface and handles authentication requests.

Adding Firebase to your project

To configure FirebaseUI and enable sign-in providers:

  1. Update or install the firebase.google.com/go package:

    go get -u firebase.google.com/go
    
  2. Add the Firebase library to your list of imports in main.go:

    firebase "firebase.google.com/go"

  3. Complete Add Firebase to your app including adding the initialization snippet to index.html.

  4. Enable the providers you would like to offer to your users for authentication in the Firebase console by clicking Authentication > Sign-in method. Then, under Sign-in providers, hover the cursor over a provider and click the pencil icon.

    Sign in providers

  5. Toggle the Enable button and, for third-party identity providers, enter the provider ID and secret from the provider's developer site. The Firebase docs give specific instructions in the "Before you begin" sections of the Facebook, Twitter, GitHub guides. After enabling a provider, click Save.

    Toggle enable button

  6. On the same page in the Firebase console, under Authorized Domains, click Add Domain and enter the domain of your app on App Engine in the following format:

    [PROJECT_ID].appspot.com
    

    Do not include http:// before the domain name.

  7. Add the firebaseConfig variable to your list of variables in main.go, and update the database URL, the project ID, and the storage bucket information from the initialization snippet you generated in the Firebase console:

    var (
    	firebaseConfig = &firebase.Config{
    		DatabaseURL:   "https://console.firebase.google.com > Overview > Add Firebase to your web app",
    		ProjectID:     "https://console.firebase.google.com > Overview > Add Firebase to your web app",
    		StorageBucket: "https://console.firebase.google.com > Overview > Add Firebase to your web app",
    	}
    	indexTemplate = template.Must(template.ParseFiles("index.html"))
    )
    

    The Config type represents the configuration used to initialize a new Firebase application.

  8. Include the following script and CSS file in the <head> tag of your page, below your customized initialization snippet in index.html:

    <script src="https://cdn.firebase.com/libs/firebaseui/2.6.2/firebaseui.js"></script>
    <link type="text/css" rel="stylesheet" href="https://cdn.firebase.com/libs/firebaseui/2.6.2/firebaseui.css" />

  9. Add the following style to your style.css file to show a user's sign-in progress:

    div.mdl-progress::after {
      display: block;
      content: 'Authenticating...';
      margin: 30px auto;
      text-align: center;
    }

  10. Add an empty <div> tag in the body of index.html which contains the providers you selected for authentication:

    <div id="firebaseui-auth-container"></div>

  11. Add another <div> tag in the body of index.html to show the user's account information and a Sign Out button which will be hidden unless a user is signed in.

    <div id="sign-in"></div>
    <div>
      <span id="account-details"></span>
      <button id="sign-out" hidden=true>Sign Out</button>
    </div>

  12. Add the id, hidden attribute, and token input to the <form> element:

    <form id="post-form" action="/" method="post" hidden=true>
      <div>Message: <input name="message" value="{{.Message}}"></div>
      <input type="hidden" name="token" id="token">
      <input type="submit">
    </form>

Authenticating users

In this application, your user will go through the following flow to log in and post a message:

  1. User clicks to sign-in to specific provider.
  2. The index.js code sends a request to Firebase to log in.
  3. Firebase sends back user information and ID token, updating the entries in the form and the web page to display current signed-in user.
  4. User then creates a message and submits the post to your application, which gets sent to the App Engine server.
  5. Server pulls the ID token from the form and sends to Firebase to receive the user information associated with the ID token.
  6. Firebase brings back the user information back to the App Engine server, which stores the post with the user information in Cloud Datastore.

Getting user information from Firebase

The frontend JavaScript code gets the user's email, name, picture, and attached token from Firebase. To ensure malicious users cannot change the user information attached to the message, only the token is sent with the post. The token is a secure way to identify the user. The application server uses the token to get the user's information.

  1. Create index.js in the static folder of your project.

  2. Add an event listener for when the user loads the page. All of your JavaScript code will reside inside this event listener.

  3. Add a handler to your Sign Out button that calls the Firebase signOut function.

  4. Configure the FirebaseUI login widget by selecting which providers you want to offer your users. This should match the list of Enabled sign-in providers you specified earlier in the Firebase console:

    // FirebaseUI config.
    var uiConfig = {
      signInSuccessUrl: '/',
      signInOptions: [
        // Leave the lines as is for the providers you want to offer your users.
        firebase.auth.GoogleAuthProvider.PROVIDER_ID,
        firebase.auth.FacebookAuthProvider.PROVIDER_ID,
        firebase.auth.TwitterAuthProvider.PROVIDER_ID,
        firebase.auth.GithubAuthProvider.PROVIDER_ID,
        firebase.auth.EmailAuthProvider.PROVIDER_ID,
        firebase.auth.PhoneAuthProvider.PROVIDER_ID
      ],
      // Terms of service url.
      tosUrl: '<your-tos-url>'
    };

  5. Handle authentication requests with the onAuthStateChanged() listener from Firebase, which is triggered when the user signs in or out:

    firebase.auth().onAuthStateChanged(function (user) {
      if (user) {
        // User is signed in.
        document.getElementById('sign-out').hidden = false;
        document.getElementById('post-form').hidden = false;
        document.getElementById('account-details').textContent =
            'Signed in as ' + user.displayName + ' (' + user.email + ')';
        user.getIdToken().then(function (accessToken) {
          // Add the token to the post form. The user info will be extracted
          // from the token by the server.
          document.getElementById('token').value = accessToken;
        });
      } else {
        // User is signed out.
        // Initialize the FirebaseUI Widget using Firebase.
        var ui = new firebaseui.auth.AuthUI(firebase.auth());
        // Show the Firebase login button.
        ui.start('#firebaseui-auth-container', uiConfig);
        // Update the login state indicators.
        document.getElementById('sign-out').hidden = true;
        document.getElementById('post-form').hidden = true;
        document.getElementById('account-details').textContent = '';
      }
    }, function (error) {
      console.log(error);
      alert('Unable to log in: ' + error)
    });

    When the user successfully signs in, Firebase creates a corresponding ID token that uniquely identifies the user. The getIdToken function retrieves the ID token from the client and attaches it the HTML form.

  6. In your index.html file, link the script file in the <head> tag of your page:

    <script src="/static/index.js"></script>

Verifying tokens on the server

Before the client can access server data, your server must verify the token is signed by Firebase. Each sign-in identity provider sends a different set of claims, but each has at least a sub claim with a unique user ID and a claim that provides some profile information, such as name or email, that you can use to personalize the user experience on your app.

  1. Add the UserID string field into your Post data structure in main.go:

    type Post struct {
    	Author  string
    	UserID  string
    	Message string
    	Posted  time.Time
    }
    

  2. In the indexHandler function, after verifying the r.Method request is POST, create a new Firebase application which will provide access to Firebase services such as Firebase Authentication. Then, verify the Firebase ID token and identify the currently signed-in user on the App Engine server using HTTPS:

    message := r.FormValue("message")
    
    // Create a new Firebase App.
    app, err := firebase.NewApp(ctx, firebaseConfig)
    if err != nil {
    	params.Notice = "Couldn't authenticate. Try logging in again?"
    	params.Message = message // Preserve their message so they can try again.
    	indexTemplate.Execute(w, params)
    	return
    }
    // Create a new authenticator for the app.
    auth, err := app.Auth(ctx)
    if err != nil {
    	params.Notice = "Couldn't authenticate. Try logging in again?"
    	params.Message = message // Preserve their message so they can try again.
    	indexTemplate.Execute(w, params)
    	return
    }
    // Verify the token passed in by the user is valid.
    tok, err := auth.VerifyIDTokenAndCheckRevoked(ctx, r.FormValue("token"))
    if err != nil {
    	params.Notice = "Couldn't authenticate. Try logging in again?"
    	params.Message = message // Preserve their message so they can try again.
    	indexTemplate.Execute(w, params)
    	return
    }
    // Use the validated token to get the user's information.
    user, err := auth.GetUser(ctx, tok.UID)
    if err != nil {
    	params.Notice = "Couldn't authenticate. Try logging in again?"
    	params.Message = message // Preserve their message so they can try again.
    	indexTemplate.Execute(w, params)
    	return
    }
    

  3. Update the post variable to populate the new UserID and Author field from the user's information:

    post := Post{
    	UserID:  user.UID, // Include UserID in case Author isn't unique.
    	Author:  user.DisplayName,
    	Message: message,
    	Posted:  time.Now(),
    }

  4. Since sign-in is required to post a message, delete the following lines from main.go:

    if post.Author == "" {
      post.Author = "Anonymous Gopher"
    }
    

Running your application locally

Run and test your application using the local development server (dev_appserver.py), which is included with Cloud SDK.

  1. From the project root directory where your application's app.yaml is located, start the local development server with the following command:

    dev_appserver.py app.yaml
    

    The local development server is now running and listening for requests on port 8080. Something go wrong?

  2. Visit http://localhost:8080/ in your web browser to view the app.

    Final

Running the local development server (dev_appserver.py)

To run the local development server, you can either run dev_appserver.py by specifying the full directory path or you can add dev_appserver.py to your PATH environment variable:

  • If you installed the original App Engine SDK, the tool is located at:

    [PATH_TO_APP_ENGINE_SDK]/dev_appserver.py
    
  • If you installed the Google Cloud SDK, the tool is located at:

    [PATH_TO_CLOUD_SDK]/google-cloud-sdk/bin/dev_appserver.py
    

    Tip: To add the Google Cloud SDK tools to your PATH environment variable and enable command-completion in your shell, you can run:

    [PATH_TO_CLOUD_SDK]/google-cloud-sdk/install.sh
    

For more information about running the local development server including how to change the port number, see the Local Development Server reference.

Making code changes

The local development server watches for changes in your project files, so it recompiles and re-launches your application after you make code changes.

  1. Try it now: Leave the local development server running and then try editing index.html to change "The Gopher Network" to something else.

  2. Reload http://localhost:8080/ to see the change.

Deploying your application

Deploy your application to App Engine using the following command from the project root directory where app.yaml is located:

gcloud app deploy

Viewing your application

To launch your browser and view your application at http://[YOUR_PROJECT_ID].appspot.com, run the following command:

gcloud app browse

Cleaning up

To avoid incurring charges to your Google Cloud Platform account for the resources used in this tutorial, complete the steps to delete your App Engine project.

Next steps

Congratulations! You built an application that can authenticate users with their email and password using Firebase Authentication. Learn how to add other features to your application by exploring the following pages:

Was this page helpful? Let us know how we did:

Send feedback about...

App Engine standard environment for Go