Search code examples
node.jsfirebasegoogle-cloud-functionshasura

Edit User's Custom Claims from Firebase


I am using firebase to generate JWT tokens to authorize access to a hasura graphql server.

I want an end user to have a callable firebase function that they can call from the app so they can change the x-hasura-role in their claims without changing other parts of their claims. I am guessing the best way to do this is to export the old custom user claims and set a new role inputted by the user.

PseudoCode:

exports.changeUserType = functions.https.onCall( async (data, context) => {
var userType = data.usertype;
// get the old user claims somehow

// check if user should be able to change their userType via a graphql query
...

// edit the user claims
return admin.auth().setCustomUserClaims(userType, {
    'https://hasura.io/jwt/claims': {
      'x-hasura-role': userType,
      'x-hasura-default-role': 'orgdriver',
      'x-hasura-allowed-roles': ['orgauditor', 'orgdriver', 'orgmanager', 'orgadmin', 'orgdirector'],
      'x-hasura-user-id': user.uid // <-- from the old claims so user can't edit
  }
});

If there is a better way to do this, maybe by grabbing a user's id from the auth database by checking who ran the function please tell me. Thank you in advance.


Solution

  • When a Firebase Authenticated user hits a Firebase Function, their uid is passed in through context. I would ensure they are authenticated first:

    if (context.auth == undefined) {
      throw new functions.https.HttpsError(
        'failed-precondition',
        'The user must be authenticated.',
      );
    }

    Then I would grab their uid:

    const uuid = context?.auth?.uid as string;

    Then you can get their user using the firebase-admin library's getAuth():

    // get user
    const user = await getAuth().getUser(uuid);

    Now finally you can set your new custom claim property:

    // set the hasura role
    return await getAuth().setCustomUserClaims(uuid, {
      ...user.customClaims,
      'x-hasura-role': userType,
    });
    

    Be sure to import:

    import { getAuth } from 'firebase-admin/auth';

    In this way you can safely know the user is authenticated and a uid exists, then you can simply grab the user and all their existing claims, then when you go to update destructure all existing claims values, and update the one value you want.

    In this way get all the user's old claims, ensure they are authenticated, retain all old claim properties, and update the one thing you want to update.

    I hope that helps out!