18

I think the Firebase Admin SDK is missing a very important function (or maybe its documentation).

TL; DR : How can you refresh custom token with the Admin SDK?

The documentation (https://firebase.google.com/docs/auth/admin/manage-sessions) says:

Firebase Authentication sessions are long lived. Every time a user signs in, the user credentials are sent to the Firebase Authentication backend and exchanged for a Firebase ID token (a JWT) and refresh token. Firebase ID tokens are short lived and last for an hour; the refresh token can be used to retrieve new ID tokens.

Ok. But how? There is no mention how to replace the refresh token with a new custom token. There are lots of documentation regarding how you can revoke a refresh token etc...

There is however a REST api function that says, (https://firebase.google.com/docs/reference/rest/auth/#section-refresh-token)

Exchange a refresh token for an ID token You can refresh a Firebase ID token by issuing an HTTP POST request to the securetoken.googleapis.com endpoint.

However, the access_token (JWT) you get from this API call is not accepted neither. And the format of the JWT's are not even similar. Below are two samples of custom tokens retrieved (decoded) : i. with the admin.auth().createCustomToken(uid) method of Admin SDK

{
  "uid": "9N5veUXXXXX7eHOLB4ilwFexQs42",
  "iat": 1521047461,
  "exp": 1521051061,
  "aud": "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
  "iss": "XXX@appspot.gserviceaccount.com",
  "sub": "XXX@appspot.gserviceaccount.com"
}

ii. with the https://securetoken.googleapis.com/v1/token?key=[API_KEY] call

{
  "iss": "https://securetoken.google.com/XXX",
  "aud": "XXX",
  "auth_time": 1521047461,
  "user_id": "9N5veUXXXXX7eHOLB4ilwFexQs42",
  "sub": "9N5veUXXXXX7eHOLB4ilwFexQs42",
  "iat": 1521051719,
  "exp": 1521055319,
  "email": "jabbar@gmail.com",
  "email_verified": false,
  "firebase": {
    "identities": {
      "email": [
        "jabbar@gmail.com"
      ]
    },
    "sign_in_provider": "password"
  }
}

There are plenty of questions raised about this topic. Maybe someone from Firebase team can answer it once and for all. See the links below

Thanks for your time!!

Kemal Yalcinkaya
  • 1,818
  • 1
  • 13
  • 25
  • @kamal Yalcinkaya has you got the solution for ios. I'm struggling for the last 3 days. if you find the solution please post here it helps me a lot. – Ramkumar chintala Dec 09 '18 at 02:04
  • Is it possible to use the REST AUTH api in the context of a *service* account? All of the other REST APIs for administration pass an oauth2 access token, not idTokens. I can sign a jwt payload with my service credentials, but what payload am I supposed to be passing, the user I want to act on behalf of? Basically I want to be able to do what i can do manually from the console but via REST HTTP and not depending on getting a short lived idToken from the client. My user doesn't have to open the app for me to be able to delete the account on console. – Ryan Romanchuk May 15 '19 at 03:23
  • 1
    I have been trying to exchange a custom token (no admin sdk) for idToken in order to take administrative actions, but it doesn't like my token. Maybe it's just a malformed payload, but i'm not even sure if i'm even conceptually correct anymore. Here's where i'm at https://gist.github.com/rromanchuk/c61aa6e59420416e9469abc7d3c1c2fe – Ryan Romanchuk May 15 '19 at 03:56

4 Answers4

8

You need to exchange a custom token for an Id Token and a Refresh token, this is mentioned here. The call should include the custom token and the property "returnSecureToken" as true. If this property is not added or is false, you will only get the ID Token.

After doing that, you can use the Refresh token to get a new ID Token once it expires. See the documentation.

Both, the custom token and the ID token, are short lived (1 hour) but the purpose is different, that is why the formats are different. You use the Id Token to make authenticated calls, whereas the custom token is only used to start the session and get an ID Token and Refresh token.

Keep in mind that if you are using an SDK, this whole work is being handled by the SDK.

Gerardo
  • 3,460
  • 1
  • 16
  • 18
  • In that case, what happens to the `verifyToken` method in the admin-sdk ? If it verifies a token being sent, will that be valid ? – Abdul Vajid Sep 20 '18 at 03:09
  • 1
    When you send a request from the client to your server, you will send the current ID Token along with the request. Then you'll use the verifyToken function to validate the request comes from a valid source. – Gerardo Sep 20 '18 at 03:54
5

You do not refresh already existing Custom Tokens but rather create new ones and exchange them for Access or Refresh Tokens. Here is how I did it in a working project I am currently using

GENERATING A CUSTOM TOKEN FROM FIREBASE CLOUD FUNCTIONS

Assuming you have your firebase project and a Cloud Functions for Firebase all set up.

This is how the Cloud Functions index.ts file would look like:

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

// Start writing Firebase Functions
// https://firebase.google.com/docs/functions/typescript

export const getCustomToken = functions.https.onRequest((request, response) => {
  if (admin.apps.length < 1) {   //Checks if app already initialized
    admin.initializeApp();
  }
  const uid = "USER_UUID"; //e.g. GVvCdXAC1FeC4WYTefcHD8So3q41

  admin.auth().createCustomToken(uid)
    .then(function(customToken) {
      console.log(customToken.toString);
      response.send(customToken);
    })
    .catch(function(error) {
      console.log("Error creating custom token:", error);
    });
});

The http GET request would look like:

https://us-central1-<ProjectID>.cloudfunctions.net/getCustomToken

The response would look like:

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJodHRwczovL2lkZW50aXR5dG9vbGtpdC5nb29nbGVhcGlzLmNvbS9nb29nbGUuaWRlbnRpdHkuaWRlbnRpdHl0b29sa2l0LnYxLklkZW50aXR5VG9vbGtpdCIsImlhdCI6MTU0MTMwOTY3MiwiZXhwIjoxNTQxMzEzMjcyLCJpc3MiOiJlbWFsbC02OWU3MEBhcHBzcG90LmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzdWIiOiJlbWFsbC02OWU3MEBhcHBzcG90LmdzZXJ2aWNlYWNjb3VudC5jb20iLCJ1aWQiOiJHVnZDZFhBQzFGZUMyV1lUZWZjSEQ4U28zcTQzIn0.hsazo6ELKbLHwPfP2d9rEykKXsBB1CdB1pCQKIVX8_Xo7tnJ0S80LQbE17ktOJ_FTr4MIllVjOLhS3kpWtKYX6Ju4kNMZ2ROLJz1bvwwgcw5unrRdQHEa3SLuyW1HvaOwKiDeYpTx2lwhZnkuBEvcoo1VcbllfYfFLIR_Y47eticONO572EL4GcIuw-RGRx1AXJR-rigRE3bj6_Ohc-PLIVXdH5v1z8fpctM2MA4NxoOZXsBDGH_ZW2Kn4NRBZYo_IT99VJU8Ypsbi_6eJguhDlbl5oWp5_NEEIEuZrN9oLaHL-PUvB8_h10lvQ6c5yP-aFKwC_EHaKBnkz7vXt8Gw

Most probably you would have to enable the IAM(Identity and Access Management) if not enabled and set up the Service Account Credential. Check the Troubleshooting.


EXCHANGING CUSTOM TOKEN FOR REFRESH & ACCESS TOKENS

The http POST request would look like:

https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyCustomToken?key=<Firebase Project Web API Key>

with a body like:

{"token":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJodHRwczovL2lkZW50aXR5dG9vbGtpdC5nb29nbGVhcGlzLmNvbS9nb29nbGUuaWRlbnRpdHkuaWRlbnRpdHl0b29sa2l0LnYxLklkZW50aXR5VG9vbGtpdCIsImlhdCI6MTU0MTMwOTY3MiwiZXhwIjoxNTQxMzEzMjcyLCJpc3MiOiJlbWFsbC02OWU3MEBhcHBzcG90LmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzdWIiOiJlbWFsbC02OWU3MEBhcHBzcG90LmdzZXJ2aWNlYWNjb3VudC5jb20iLCJ1aWQiOiJHVnZDZFhBQzFGZUMyV1lUZWZjSEQ4U28zcTQzIn0.hsazo6ELKbLHwPfP2d9rEykKXsBB1CdB1pCQKIVX8_Xo7tnJ0S80LQbE17ktOJ_FTr4MIllVjOLhS3kpWtKYX6Ju4kNMZ2ROLJz1bvwwgcw5unrRdQHEa3SLuyW1HvaOwKiDeYpTx2lwhZnkuBEvcoo1VcbllfYfFLIR_Y47eticONO572EL4GcIuw-RGRx1AXJR-rigRE3bj6_Ohc-PLIVXdH5v1z8fpctM2MA4NxoOZXsBDGH_ZW2Kn4NRBZYo_IT99VJU8Ypsbi_6eJguhDlbl5oWp5_NEEIEuZrN9oLaHL-PUvB8_h10lvQ6c5yP-aFKwC_EHaKBnkz7vXt8Gw","returnSecureToken":true}

The response would look like:

{
    "kind": "identitytoolkit#VerifyCustomTokenResponse",
    "idToken": "eyJhbGciOiJSUzI1NiIsImtpZCI6Ijk4Njk0NWJmMWIwNDYxZjBiZDViNTRhZWQ0YzQ1ZWU0ODMzMjgxOWEiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vZW1hbGwtNjllNzAiLCJhdWQiOiJlbWFsbC02OWU3MCIsImF1dGhfdGltZSI6MTU0MTMxMDkzOSwidXNlcl9pZCI6IkdWdkNkWEFDMUZlQzJXWVRlZmNIRDhTbzNxNDMiLCJzdWIiOiJHVnZDZFhBQzFGZUMyV1lUZWZjSEQ4U28zcTQzIiwiaWF0IjoxNTQxMzEwOTM5LCJleHAiOjE1NDEzMTQ1MzksImVtYWlsIjoiYUBhLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6eyJlbWFpbCI6WyJhQGEuY29tIl19LCJzaWduX2luX3Byb3ZpZGVyIjoiY3VzdG9tIn19.KnMU0SHoMkMOwGBOfwnwMYCyFAGZycC1zA5pva47i4TylGdZyz-93h3KyWA_EYHGZtI29YWfarUG0-6K_sLORttMzKy3t9jBcvhgWN8G9zE8DHg0DuOeaDxDfKY8-W-CBgh8wiTSOfz-CRTT9spXoP_9PigdWFKiwmgP_vvOGStONFjUMh2hSNaRhHAj_0nlFxQuBsoP9eV3uGm1ycC3z8e5AHVbvE7VgIxK27OcKY4z9n1IrBADp9gxM6ESlOYE2y_bfP2i_WIv_4ZQ3fA0aeKhSjhO7AhKUVvZ8FphqzlHF_q966QIglLf9vkVVzQCo-9YdD9j_GRea88tj3P5PQ",
    "refreshToken": "AEXAG-dZJD0zYr-RehU4qXLDRwf1SueYHPeQv6WHQ-w3SW8oFPU27EwdcrBcRP1p4hbTMIjeTTOub9buL20c3dxQvjpCzI4gda73jhHhigLFq6LZGU_S0VXW-9_gG_Vrcx25g2SAiMEt3WuLlP5h0R4h6Eo_DeX2F15vGQMxqplqcOSNGptN-r0",
    "expiresIn": "3600",
    "isNewUser": false
}

Good luck,

Community
  • 1
  • 1
ArtiomLK
  • 2,120
  • 20
  • 24
  • 1
    where do i use refresh token? – softmarshmallow Feb 21 '19 at 08:45
  • @uzu you would use it anywhere you need it? What do you mean by `where do i use refresh token?` – ArtiomLK Feb 22 '19 at 00:52
  • Will this work if the user was not created from a custom token? I've tried exchanging a jwt custom token, signed with the appropriate service account credentials, and exchanging it for an idToken https://firebase.google.com/docs/reference/rest/auth/#section-verify-custom-token but it's never happy. i assumed my jwt payload is incorrect but i've rtfm many times now. It's pretty difficult because this layer is obfuscated by SDKs so not many resources that i can find. Also, I don't really understand why all the other admin apis use access tokens in the form of Authorization header – Ryan Romanchuk May 15 '19 at 03:44
  • I need to dig through those libraries and find the exact data structure they are using to sign the token. This is the best i've come up with https://gist.github.com/rromanchuk/c61aa6e59420416e9469abc7d3c1c2fe – Ryan Romanchuk May 15 '19 at 03:50
  • @ArtiomLK where does user id come from? I am talking about "const uid = "USER_UUID"; //e.g. GVvCdXAC1FeC4WYTefcHD8So3q41" Did you copy it from Google Firebase Console somehow? – Jim C Apr 21 '20 at 01:39
  • 1
    @JimC yes, the `USER_UUID` was randomly assigned by Firebase and it is unique to the Firebase project. I hardcoded it in my method for testing purposes. I copied it from the firebase console – ArtiomLK Apr 21 '20 at 01:44
  • 1
    @RyanRomanchuk, yes it will work for users not created from a custom token. It does not matter how you created the users, the custom token method is used to provide access to someone or something that you do not want to give your user credentials. – ArtiomLK Apr 21 '20 at 01:49
  • @ArtiomLK thanks for promptly answer. Sorry, where exactly did you copy from in Firebase Console? Did you (1) https://console.firebase.google.com/project/firetestjimis/authentication/users (2) click ADD USER (3) after added a new user you copied User UID? – Jim C Apr 21 '20 at 02:15
  • @ArtiomLK do you know how I can use idToken to get access to FireStore? I mean, after I post to https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyCustomToken?key= with CustomToken in body I get idToken back. I understand I can use this idToken in order to access Firestore from related project, right? – Jim C Apr 21 '20 at 02:27
  • 1
    @JimC In the Firebase Console -> Authentication tab there is a tableView with the UID column. Correct, after you exchange the Custom Token for an AuthToken you are able to use it the same way you do when you are signed in with your email and pass. – ArtiomLK Apr 21 '20 at 02:58
  • @ArtiomLK I am stuck to be able to use the idToken in order to post to FireStore. I created a new question https://stackoverflow.com/questions/61335303/posting-to-firestore-with-custom-token-caused-request-had-invalid-authentication. Please, take a look at – Jim C Apr 21 '20 at 03:08
1

How can you refresh custom token with the Admin SDK?

Answer: If you are using the Android SDK for Firebase, you should NEVER have to do this. If you are asking this question and are using the iOS or Android SDKs you probably have a set up issue. The SDKs will handle all the refreshing of tokens if you are SETUP CORRECTLY. I was using the FirebaseAuth's signInWithCustomToken and was running into the same issue.

An overview of Firebase tokens
The SDK has 1 hour to USE THE CUSTOM TOKEN If you read the whole conversation and ignore the complaining, it lays out the issue. See samtstern comment around July 6th

Once FirebaseAuth's signInWithCustomToken function is called, the SDK will take care of keeping the tokens up to date IF YOU ARE SETUP correctly. For more info

The SHA1 cert from your ANDROID app has to be in your Firebase Admin console. Once you add the SHA1 cert you will need to download the google-services.json file and add it to your app. I took over a firebase account for an app still in development and ran into this problem.

Jeremy House
  • 141
  • 1
  • 3
  • What about this scenario.. How can you delete a firebase account with only the following things: The firebase user id, REST, all the appropriate service account credentials and with all the capable crypto tools to authenticate yourself. Here's what you dont have: language specific SDKs, user idTokens. Also: those users accounts are not created from custom tokens, they were created by 1st class firebase providers – Ryan Romanchuk May 15 '19 at 03:34
  • I was addressing the issue around custom tokens as the documentation and solutions on the internet can confuse folks easily. I would create a new post for your question on deleting accounts. – Jeremy House May 16 '19 at 15:26
1

You didn't mention programming language, but in Java, something like this works for me:

// Use the user ID with the Firebase Admin SDK to generate a custom token
FirebaseAuth firebaseAuth = FirebaseAuth.getInstance();
String customToken = firebaseAuth.createCustomToken(userId);

// Now use the Firebase REST API to exchange the custom token for an ID and refresh token
JsonObject json = new JsonObject();
json.addProperty("token", customToken);
json.addProperty("returnSecureToken", true);
String postDataStr = GSON.toJson(json);
Map<String, String> headers = new HashMap<>();
headers.put("Content-Type", "application/json");

// Make a request to the Firebase client-side API to exchange the credentials for an ID token, Refresh Token, etc.
URI uri = new URI("https://identitytoolkit.googleapis.com/v1/accounts:signInWithCustomToken?key={API_KEY_HERE}");

. . .

Make the POST request, etc. Note that the response should be parsed as JSON and then the the ID token is in ".idToken" and the refresh token is in ".refreshToken".

Todd
  • 99
  • 1
  • 4