Support Firebase JWT token verification

Hi @jdgamble555, you are pretty close. Now you need to insert custom claims in order to extract the email which you have used in the auth query. Custom claims will be inserted at the time of minting new jwt token and you need to host a firebase function for that. Please refer to this series of videos for further clarification. Your firebase functon will be something like this:

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

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

where "https://dgraph.io/jwt/claims" is your Namespace.

Your token will look something like:

{
  "https://dgraph.io/jwt/claims": {
    "email": "youremail@something.com"
  },
  "iss": "https://securetoken.google.com/project-id",
  "aud": "fir-project1-259e7",
  "auth_time": 1606373016,
  "user_id": "17GoxvuNBZW9a9JS.......",
  "sub": "17GoxvuNBZW9a9JSvw.........",
  "iat": 1606373017,
  "exp": 1606376617,
  . . . . .

Hi @minhaj. I was able to create the function fine and generate a new custom id by calling httpsCallable(ā€˜addAdminRoleā€™); and getting the id from user.getIdTokenResult(true) (although I am not sure I needed to considering the email address is by default in the token).

So I am sending this custom claim header to X-Firebase-Token: token as the header to my dgraph url:

aud: "my-app"
auth_time: 1606358478
email: "myemail@something.com"
email_verified: true
exp: 1606401418
firebase: {identities: {ā€¦}, sign_in_provider: "google.com"}
https://dgraph.io/jwt/claims: {email: "myemail@something.com"}
iat: 1606397818
iss: "https://securetoken.google.com/my-app"
name: "Jonathan Gamble"
picture: "https://lh3.googleusercontent.com/a-/AOh14GgYcf9DF3Lr0nU2jMrdc7zPopDIPx0txQIS5EbikQ=s96-c"
sub: "F2isYDFZAdZPNq3Ql5quSesUpDrW43"
user_id: "F2isYDFZAdZPNq3Ql5esquSUpDrW43"

And my Dgraph Schema:

#Dgraph.Authorization {
"VerificationKey":"",
"Header":"X-Firebase-Token",
"Namespace":"https://dgraph.io/jwt/claims",
"JWKUrl":"https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com",
"Algo":"",
"Audience":["my-app"]
}

According to github, it looks like it is JWKUrl, not jwkurl or jwkUrl, however, none of them seem to give me resultsā€¦

It is also worth noting than when I change #Dgraph.Authorization to # Dgraph.Authorization (added a space), I get this error in the schema console resolving updateGQLSchema failed because required field missing in Dgraph.Authorization: \`Verification key\` \`Algo\ (not sure if this is relevant), so I get rid of the space thereā€¦

What I am missing here? It is hard to debug without any errors on the dgraph endā€¦

Thanks,
J

Hey @jdgamble555
1- All of the JWKUrl, jwkurl or jwkUrl work fine in this case.
2- You need to put space between # and Dgraph.Authorization.
3- The error you are getting is due to not providing JWKURL, in that case it expects Verification Key and Algo.

Hi @minhaj

You can blatantly see I have the JWKURL and the space, I am getting the error.

Thanks,

J

Which version of Dgraph you are on ?

@minhaj Slash DGraph. Is there more than one version?

J

Slash is using Dgraph v20.07 in production. Authentication with JWKURL is not supported on this version. It is a recent feature and will be a part of the upcoming release.

@minhaj I see. Thanks for all your help!

When is the upcoming release?

J

You can expect in 1 or 2 weeks if all goes well.

@minhaj - Okay, now that Slash Dgraph is updated, I am still having problems:

Here is my schema:

type Task @withSubscription @auth(
    query: { 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 @withSubscription {
    username: String! @id @search(by: [hash])
    name: String
    tasks: [Task] @hasInverse(field: user)
}

# Dgraph.Authorization {"Header":"X-Firebase-Token","Namespace":"https://dgraph.io/jwt/claims","JWKURL":"https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com","Audience":["my-app"]}

My Function ā€“ deployed and working ā€“

import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';

admin.initializeApp();

exports.addAdminRole = functions.https.onCall(async (data: any,) => {

  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: any) => {
    return err;
  });
});

I call the admin function:

const callable = firebase.functions().httpsCallable('addAdminRole');
callable({ email: 'myemail@gmail.com' }).then((r) => {
  console.log(r);
});

I get message: success! in the console, so this worksā€¦

thenā€¦

firebase.auth().onAuthStateChanged(async user => {
      if (user) {
        user.getIdTokenResult(true).then(idToken => {
          this.token = idToken.claims;

          // this prints the token fine with custom claim...
          console.log(this.token);
          ...
        });
      }
    });
  }

andā€¦

client.query({
  query: MY_QUERY,
  context: {
    headers: {
      "X-Firebase-Token": this.token
    }
  }
});

I do not get any results back. However, when I remove the query rule, everything works fineā€¦ what am I missing?

Again, the problem here is I canā€™t see what is going through on DGraphā€™s end to debugā€¦

Note: I do have a Task in the db with username myemail@gmail.com pre-populatedā€¦

Anyone gotten this to work on any platform in anyway? Are there any plans for tutorials for firebase authentication? If so, any time soon? This is HUGE for a lot of usersā€¦

Hey @jdgamble555, Sorry for the late reply. There could be 2 problems.
1 - Either Token doesnā€™t have properly mentioned custom claims. (This could be verified bt decoding the JWT).
2 - Or you are not able to integrate it properly with your application.

When I was exploring authentication with firebase, I used this repo for guidance. Hope you can get help from it.

Hi @minhaj

I tested the custom claim and it does decode properly. I have been using regular firebase authentication for years, so that is not the problem. Is it possible there is a third option where DGraph does not work properly? Has anyone ever tested this specifically for firebase authentication using custom claims? While I know it takes time to write documentation, is there a firebase maybe github specific working example using firebase auth?

Thanks
J

Hi @jdgamble555, I have rechecked the working of authentication with firebase and jwkurl. Everything seems good to me. Please wait for sometime while we develop a working example to demonstrate clearly the mentioned steps.

2 Likes

Hello!

Iā€™m trying to get Firebase Auth working with Slash Dgraph, and itā€™s not clear to me how I access ā€œroot-levelā€ payload values in the JWT. For example, a live token I got from firebase auth has this payload:

  "payload": {
    "iss": "https://securetoken.google.com/zoraw-sb",
    "aud": "zoraw-sb",
    "auth_time": 1609372522,
    "user_id": "Hr388KI7J<snip>",
    "sub": "Hr388KI7J<snip>",
    "iat": 1609542519,
    "exp": 1609546119,
    "email": "marcello@cellosoft.com",
    "email_verified": true,
    "firebase": {
      "identities": {
        "email": [
          "marcello@cellosoft.com"
        ]
      },
      "sign_in_provider": "password"
    }
  },

Namely, I want to pull out user_id (and maybe email/email_verified). I tried leaving out Namespace but itā€™s a required field.

If Iā€™m understanding all this correctly, it seems like I need to introduce my own intermediate server that somehow rewrites the standard Firebase JWT and adds subfields to the payload, duplicating the data already there?

At the end of the day I want dgraph to know the authenticated user_id so I can control read/write access to data.

Edit: Oh and something that would be great is a way to debug this. Itā€™d be great to see whether a JWT is being interpreted correctly and what variables itā€™s defining.

Thanks in advance!

Marcello

I am still not very familiar with firebase, but am starting to learn. I have experience with Auth0 and I believe what you need is something similar to Auth0 Rules/Hooks. I glanced over firebase documentation and I think I am on the right track here comparing this functionality with Cloud Functions in Firebase. I believe you need to add a function that Control Access with Custom Claims. It appears you can Trigger a function on user creation that can access user attributes and put it into a custom claims that is then readable by Slash GraphQL.

Hope this helps with some direction.

2 Likes

Cheers. Iā€™m gonna try that approach now.

Itā€™d be great to have access to the existing claims, rather than adding duplicate ones!

You might be able to follow the post GraphQL authorization using standard claims - #8 by pawan I do not know the status of this.

1 Like

My problem is that I have been using firebase for years and cannot get DGraph to accept any sort of Custom Claim. Adding an email to the claim via ā€œhttps://dgraph.io/jwt/claimsā€ seems overkill to me considering it is already in the regular tokenā€¦ I donā€™t know why you need a function for that at all. However, adding other information like roles etc seems worthwhile. One way could be to add it to:

functions.auth.user().onCreate((user)

that firebase functionā€¦

I created a simple todo app in SVELTE that works with Typescript, Subscriptions, and Firebase Auth:

I added some basic instructions to get it going. I made examples for both apollo and urql. It is easy to see urql is much faster. The app just uses login with google. Thatā€™s it.

In the 21.03 version, I will update it to use Standard Claims. This is important, as firebase functions are not free, but Firebase Auth is.

Here is a possible firebase function to create users, please let me know if it works:

import * as functions from "firebase-functions";
import * as admin from "firebase-admin";
import { createClient, gql } from "@urql/core";

admin.initializeApp();

const CREATE_CLAIM = (email: string | undefined, isNewUser = false) => {
  return {
    "https://dgraph.io/jwt/claims": {
      USER: email,
      CREATE_USER: isNewUser,
    },
  };
};

const ADD_USER = gql`
  mutation addUser($user: AddUserInput!) {
    addUser(input: [$user]) {
      user {
        username
      }
    }
  }
`;

const DGRAPH = "YOUR_ENDPOINT";

exports.addUserClaim = functions.https.onCall(async (data: any) => {
  return admin
    .auth()
    .getUserByEmail(data.email)
    .then((user: admin.auth.UserRecord) => {
      return admin
        .auth()
        .setCustomUserClaims(user.uid, CREATE_CLAIM(user.email));
    })
    .catch((err: any) => {
      return err;
    });
});

exports.addUser = functions.auth
  .user()
  .onCreate(async (user: admin.auth.UserRecord) => {
    const token = await admin
      .auth()
      .createCustomToken(user.uid, CREATE_CLAIM(user.email, true));
    const client = createClient({
      url: DGRAPH,
      fetchOptions: {
        headers: {
          "X-Auth-Token": token,
        },
      },
    });

    const result = await client
      .mutation(ADD_USER, {
        user: {
          username: user.email,
          name: user.displayName,
        },
      })
      .toPromise();

    if (result.error) {
      console.log(result.error);
    }
   return null;
  });

You will notice the firebase is a little faster. I believe it automatically updates the dom->cache->database before the database while graphql goes database->dom->cacheā€¦ but I could be wrong on this. I believe you could write a service worker, or some kind of front end cache, that runs automatically with DGraph to cache better and speed it up.

Anyone have any general purpose ideas to speed it up, please let me know! You really need a library that could do this automatically so that no one thinks about this! Firebase does all this automatically. The future of this database could be better!

Thanks @minhaj for helping me on this!

Let me know if there are any issues.

J

3 Likes