4
votes

I have a user with UID 1 where the custom claims are set as,

frompos=true

I am setting new custom claims to this user from the ADMIN SDK for java the following way:

Map<String,Object> claims = new HashMap<>();

claims.put("frompos",false);

FirebaseAuth.getInstance().setCustomUserClaimsAsync("1", claims).get(10000,
                        TimeUnit.MILLISECONDS);

I print the claims on the server side to check if the claims are set:

UserRecord user = FirebaseAuth.getInstance().getUserAsync("1").get(10000,
                        TimeUnit.MILLISECONDS);

LOG.debug("user new claims " + user.getCustomClaims());

The result as expected is that the claims get set:

user new claims {frompos=false}

Now on the android sdk side, I have the user already logged in so I am refreshing the ID token manually to propagate the claims as the docs say (https://firebase.google.com/docs/auth/admin/custom-claims)

FirebaseAuth.getInstance().getCurrentUser().getIdToken(true).addOnCompleteListener(new OnCompleteListener<GetTokenResult>() {
         @Override
         public void onComplete(@NonNull Task<GetTokenResult> task) {
                    if(task.isSuccessful()){
                    Log.d("FragmentCreate","Success refreshing token "+(FirebaseAuth.getInstance().getCurrentUser()==null));

                    Log.d("FragmentCreate","New token "+task.getResult().getToken());

                        }
                    }
         }).addOnFailureListener(new OnFailureListener() {
                    @Override
                    public void onFailure(@NonNull Exception e) {
                        Log.d("FragmentCreate","Failure refreshing token "+(FirebaseAuth.getInstance().getCurrentUser()==null)+" "+e.toString());
                    }
         });

Now I use the printed Id Token printed here and verify it on server side and print the claims from it

 FirebaseToken tokenTest = FirebaseAuth.getInstance(ahmedabadRepoApp).verifyIdTokenAsync(token).get(10000,TimeUnit.MILLISECONDS);

 LOG.debug("Token claims are "+tokenTest.getClaims());

But the claims printed here are:

{"aud":"ahmedabadrepo","auth_time":1514724115,"email_verified":false,"exp":1514730425,"iat":1514726825,"iss":"https://securetoken.google.com/ahmedabadrepo","sub":"1","frompos":true,"user_id":"1","firebase":{"identities":{},"sign_in_provider":"custom"}}

Thus the frompos value did not propagate to the client sdk even though I did refresh the Id token manually.

3
Are you forcing token refresh after the claims have been set on the admin side? At some point it looks like you successfully set "frompos" as it appears in the ID token but is showing as a different value.bojeil
I login with custom token with frompos set to true. Then change it via admin and print there. Then i force a refresh on client. Here the value still remains true even if the admin sdk set it falseKushan
Do you have any updates on this?Chad Bingham
Unfortunately not, i had raised a ticket with their support but they couldn't replicate it. Had to give up the idea and am now using a new way in my process. This seems like a definite bug to meKushan

3 Answers

2
votes

I was having the same issue in angular - I set the claim using the Admin SDK on the server, but then they would not be in the user on the client.

Using the following I can see the claims in the payload:

  this.firebaseAuth.auth.currentUser.getIdToken().then(idToken => {

    const payload = JSON.parse(this.b64DecodeUnicode(idToken.split('.')[1]))

    console.log(payload);
    }

  )

b64DecodeUnicode(str) {
    return decodeURIComponent(atob(str).replace(/(.)/g, function (m, p) {
        var code = p.charCodeAt(0).toString(16).toUpperCase();
        if (code.length < 2) {
            code = '0' + code;
        }
        return '%' + code;
    }));
  }

Here is a good write up of this where I copied the above:

At the moment the client-side code must parse and decode the user’s ID token to extract the claims embedded within. In the future, the Firebase client SDKs are likely to provide a simpler API for this use case.

Relevant info from Firebase Docs:

Custom claims can only be retrieved through the user's ID token. Access to these claims may be necessary to modify the client UI based on the user's role or access level. However, backend access should always be enforced through the ID token after validating it and parsing its claims. Custom claims should not be sent directly to the backend, as they can't be trusted outside of the token.

Once the latest claims have propagated to a user's ID token, you can get these claims by retrieving the ID token first and then parsing its payload (base64 decoded):

// https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding
firebase.auth().currentUser.getIdToken()
  .then((idToken) => {
     // Parse the ID token.
     const payload = JSON.parse(b64DecodeUnicode(idToken.split('.')[1]));
     // Confirm the user is an Admin.
     if (!!payload['admin']) {
       showAdminUI();
     }
  })
  .catch((error) => {
    console.log(error);
0
votes

This might help: https://stackoverflow.com/a/38284384/9797228

firebase.auth().currentUser.getIdToken(true)
0
votes

The client sdk is caching the old token (old claims).

You should add a mechanism to refresh it after changing the claims (eg. push notification) or just wait for the old token to expires or user to lougout and login again.

It's explained here https://youtu.be/3hj_r_N0qMs?t=719

Edit

You can force the sdk to refresh the token using firebase.auth().currentUser.getIdToken(true)