Ask a Question

Using Firebase Authentication

In this step, we will add Firebase authentication per the sample Todo app with Firebase Authentication.

Create Project

Let’s start by going to the Firebase console and create a new project (Todo-app).

In the Authentication section, enable Email/Password signin. You can add a custom domain to Authorized domains below according to where you want to deploy your app. By default localhost is added to the list.

Authentication Section

Now we want to use the JWT that Firebase generates, but we also need to add custom claims to that token which will be used by our authorization rules.

To add custom claims to the JWT we need to host a cloud function which will insert claims into the JWT on user creation. This is our cloud function which inserts USER: email claim under the Namespace https://dgraph.io/jwt/claims.

const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();

exports.addUserClaim = functions.https.onCall((data, context) => {
	return admin.auth().getUserByEmail(data.email).then(user=>{
		return admin.auth().setCustomUserClaims(user.uid, {
			"https://dgraph.io/jwt/claims":{
				"USER": data.email
			}
		});
	}).then(() => {
		return {
			message: `Success!`
		}
	}).catch(err => {
		 return err
	})
})

Using the Firebase CLI

Clone the Todo Firebase app repo and try to deploy the function to the Firebase project created above.

git clone https://github.com/dgraph-io/graphql-sample-apps.git
cd graphql-sample-apps/todo-react-firebase
npm i
  • Install the Firebase CLI tool npm install -g firebase-tools.
  • Login into Firebase from the CLI firebase login.
  • Run firebase init functions then select an existing project (that you created above).
  • Select language as JavaScript for this example.
  • Replace index.js with the snippet above.
  • Deploy the function `firebase deploy –only functions.

Please refer to the deployment guide for more info.

Firebase CLI

Create Webapp

Create a web app from your Firebase project settings page.

Firebase Create Webapp

After creating that, copy the config from there.

Firebase Config

Setup your Firebase configuration and Dgraph Cloud endpoint in the config.json. It looks like this:

{
    "apiKey": "your-firebase-apiKey",
    "authDomain": "your-firebase-authDomain",
    "projectId": "your-firebase-projectId",
    "storageBucket": "your-firebase-storageBucket",
    "messagingSenderId": "your-firebase-messagingSenderId",
    "appId": "your-firebase-appId",
    "graphqlUrl": "your-graphql-endpoint"
}

Authentication with Firebase is done through the JWKURL, where the JSON Web Key sets are hosted by Firebase. Since Firebase shares the JWKs among multiple tenants, you must provide your Firebase project-Id to the Audience field. So the Dgraph.Authorization header will look like this:

{"Header":"your-header", "Namespace":"namespace-of-custom-claims","JWKURL": "https://www.googleapis.com/service_accounts/v1/jwk/[email protected]", "Audience":[your-projectID]}

You don’t need to set the VerificationKey and Algo in the Authorization header. Doing so will cause an error.

Update the schema, add the Authorization header (update the project-Id) -

type Task @auth(
		query: { rule: """
        query($USER: String!) {
            queryTask {
                user(filter: { username: { eq: $USER } }) {
                    __typename
                }
            }
        }"""}
		add: { rule: """
        query($USER: String!) {
            queryTask {
                user(filter: { username: { eq: $USER } }) {
                    __typename
                }
            }
        }"""}){
    id: ID!
    title: String! @search(by: [fulltext])
    completed: Boolean! @search
    user: User!
}
type User {
  username: String! @id @search(by: [hash])
  name: String
  tasks: [Task] @hasInverse(field: user)
}

# Dgraph.Authorization {"JWKUrl":"https://www.googleapis.com/service_accounts/v1/jwk/[email protected]", "Namespace": "https://dgraph.io/jwt/claims", "Audience": ["your-project-id"], "Header": "X-Auth-Token"}

Resubmit the updated schema to Dgraph or Dgraph Cloud.

React App

For an example of how to initialize the Firebase app with the updated configuration (config) settings, see base.js.

import firebase from "firebase/app";
import "firebase/auth";
import config from "./config.json";


const app  = firebase.initializeApp({
  apiKey: config.apiKey,
  authDomain: config.authDomain,
  projectId: config.projectId,
  storageBucket: config.storageBucket,
  messagingSenderId: config.messagingSenderId,
  appId: config.appId
});

export default app;

To understand how the client gets the token and sends it along with each GraphQL request, see Auth.js. We can see from the code that whenever there will be state change, currentUser will be set to the new user and context will return App with the new idToken. App will initialize the Apollo Client which will send this idToken in header along with every GraphQL request.

import React, { useEffect, useState } from "react";
import app from "./base.js";
import firebase from "firebase/app";
import "firebase/functions";

import App from "./App";
export const AuthContext = React.createContext();

export const AuthProvider = ({ children }) => {
  const [currentUser, setCurrentUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [idToken, setIdToken] = useState("");
  const addUserClaim = firebase.functions().httpsCallable('addUserClaim');

  useEffect(() => {
    app.auth().onAuthStateChanged( async user => {
      setLoading(false)
      setCurrentUser(user)
      if (user) {
        addUserClaim({email: user.email})
        const token = await user.getIdToken(); 
        setIdToken(token);
      }
    });
  }, []);

  if(loading){
    return <>Loading...</>
  }
  return (
    <AuthContext.Provider
      value={{
        loading,
        currentUser,
      }}
    >
      {children}
      <App idToken = {idToken}/>
    </AuthContext.Provider>
  );
};

To review the Apollo Client setup, see App.js.

...

const createApolloClient = token => {
  const httpLink = createHttpLink({
    uri: config.graphqlUrl,
    options: {
      reconnect: true,
    },
  });

  const authLink = setContext((_, { headers }) => {
    // return the headers to the context so httpLink can read them
    return {
      headers: {
        ...headers,
        "X-Auth-Token": token,
      },
    };
  });

  return new ApolloClient({
    link: authLink.concat(httpLink),
    cache: new InMemoryCache()
  });
}

const App = ({idToken}) => {
  const { loading } = useContext(AuthContext);
  if (loading) {
    return <div>Loading...</div>;
  }
  console.log(idToken)
  const client = createApolloClient(idToken);
  return (
    <ApolloProvider client={client}>
      <div>
        <Router history={history}>
        <header className="navheader">
          <NavBar/>
        </header>
        <Switch>
        <PrivateRoute path="/" component= {TodoApp} exact />
        <PrivateRoute path="/profile" component={Profile} exact/>
        <Route exact path="/login" component = {Login} />
        <Route exact path="/signup" component={SignUp} />
      </Switch>
      </Router>
    </div>
    </ApolloProvider>
  );
}

export default App

Now that we have a basic understanding of how to integrate Firebase authentication in our app, let’s see it in action!

npm start

SignUp Screen

Todos Screen