90

I use the JS library call firebase.auth().signInWithEmailAndPassword(email, password) and get back a User object. The User object contains a refreshToken.

I use curl 'https://docs-examples.firebaseio.com/rest/saving-data/auth-example.json?auth=TOKEN' to make calls to Firebase.

The token will eventually expire. In order to make it look like the application (iOS and macOS) has persistent login, I want to refresh the token, how do I do that with using either the REST or JS library? I can't find any calls in the documentation that allow me to use the refreshToken to get a new token.

kgaidis
  • 14,259
  • 4
  • 79
  • 93

4 Answers4

70

When you make call from a browser .getIdToken(true) will automatically refresh your token. Make call like this:

firebase.auth().currentUser.getIdToken(/ forceRefresh / true)
  .then(function(idToken) {
    
  }).catch(function(error) {

});

More info here https://firebase.google.com/docs/reference/js/firebase.User#getIdToken

krupesh Anadkat
  • 1,932
  • 1
  • 20
  • 31
Yevgen
  • 4,519
  • 3
  • 24
  • 34
  • 10
    Wouldn't currentUser be NULL on a fresh launch of an app? I don't know how it is on a regular browser, but I do not store any cookies or local data. I only store the refreshToken and anything else that I specifically need. – kgaidis Jul 06 '16 at 21:13
  • 2
    Login is needed to be performed first to use that code. If you use sign in with firebase it should work. – Yevgen Jul 07 '16 at 05:52
  • 2
    This! But `.getToken` is deprecated now, you must use `.getIdToken`. – Nick Rameau Jul 12 '17 at 04:20
  • I got 403 (Permission denied error) even though currentUser is not null (i.e. the user was signed in). Do you know the reason ? – Takamitsu Mizutori Mar 13 '20 at 08:51
  • 2
    @TakamitsuMizutori I had the same issue, the token couldn't be refreshed. I solved it by enabling the Token Service API for my Google API key. – Louis Ameline Apr 18 '20 at 16:33
  • @Louis Ameline It works! Never thought about the API key. Thank you so much – Takamitsu Mizutori May 04 '20 at 01:59
  • @stevehs17 Could you please add your sources, and let everyone know why you think this is wrong? Did you experience this yourself, or is this something that you saw written in the official doc? Thanks – Gunee Jul 05 '22 at 11:10
  • @Gunee My apologies, but I now think my previous comment (which I will delete) was wrong: getIdToken() always returns a valid token. This is based on my personal experience using that function in a React Native app. (Thank you for calling my attention to my incorrect comment.) – stevehs17 Jul 06 '22 at 03:59
60

** UPDATE ** this is also now documented in Firebase REST docs under Exchange a refresh token for an ID token section:

https://firebase.google.com/docs/reference/rest/auth/#section-refresh-token


Currently the only way I found to do this is here: https://developers.google.com/identity/toolkit/reference/securetoken/rest/v1/token

You must make an HTTP request:

POST https://securetoken.googleapis.com/v1/token?key=YOUR_KEY

Where YOUR_KEY can be found in the Google developers console > API Manager > Credentials. It's under the API Keys section.

Make sure request body is structured in the following format:

grant_type=refresh_token&refresh_token=REFRESH_TOKEN

Where REFRESH_TOKEN is the refresh token from Firebase user object when they signed in.

You must set the header Content-Type: application/json or you will get errors (e.g. "MISSING_GRANT_TYPE").

The POST call will return a new idToken (used to be called access_token)

HexaCrop
  • 3,863
  • 2
  • 23
  • 50
kgaidis
  • 14,259
  • 4
  • 79
  • 93
  • Should this request work for refreshing Gmail API's token? @kgaidis – ArtStyle Aug 27 '16 at 00:46
  • @ArtStyle I am not familiar with Gmail API, so I don't know – kgaidis Aug 27 '16 at 01:53
  • This works but unfortunately does not trigger firebase's [onIdTokenChanged](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#onIdTokenChanged) listener... I feel it should – Luiz Oct 08 '17 at 22:04
  • I am using the same way and i am getting the id_token. But when i try to fetch values from the DB using the id_token , i get an error saying 'permission denied'. – Kumar KS Nov 16 '18 at 16:13
  • how to use the obtained data to set the new token? – Killy Dec 05 '18 at 11:13
  • Will the new, google-identity token be invalidated if the firebase user is deleted or invalidated? @kgaidis – Lane Sep 03 '19 at 00:53
  • 1
    I got 403 (Permission denied) error when I do this. Can I call this API from web client javascript ? – Takamitsu Mizutori Mar 13 '20 at 08:50
  • 2
    `access_token` = `id_token` – Rakka Rage May 30 '20 at 15:48
  • Im using Restrict keys for requests to the specified websites. But If add the ```https://securetoken.googleapis.com``` to my restricted list I'm not able to call the API and I have to remove all other restricted URLs to make a call. In that case, my key would be accessible for every address. Any suggestion? – i.karayel Oct 15 '20 at 13:40
  • 1
    thanks so much you made my day, I wish I could have voted a 100 times. My app kept on yelling token expired throughout development, till you came to my aid – Chukwuemeka Maduekwe Nov 12 '20 at 18:14
  • 2
    For me, it did not work with `Content-type: application/json` and I had to go back to the old `x-www-form-urlencoded`. – Spoutnik16 Apr 20 '22 at 07:05
15

I guess most people here are looking for a way to persist their authentication not in a browser but e.g. on a node backend. Turns out there actually is a way to do this:

  1. Trade the refresh-token for an access-token (using google's public api)
  2. Trade the access-token for a custom-token (using a firebase-function, see below)
  3. Login with custom-token

Here's the essence of the code:

const requestP = require('request-promise');
const fsP = require('fs').promises;

const refreshToken = await fsP.readFile('./refresh_token.txt');
const res = await requestP.post({
  headers: {'content-type': 'application/x-www-form-urlencoded'},
  url: 'https://securetoken.googleapis.com/v1/token?key=' + firebaseConf.apiKey,
  body: 'grant_type=refresh_token&refresh_token=' + refreshToken,
  json: true
});
const customToken = await requestP.post({
  headers: {'content-type': 'text/plain'},
  url: 'https://<yourFirebaseApp>.cloudfunctions.net/createCustomToken',
  body: {token: res.access_token},
  json: true
});
await firebaseApp.auth().signInWithCustomToken(customToken);

And the firebase function:

export const createCustomToken = functions.https.onRequest(async (request, response) => {
  response.set('Access-Control-Allow-Origin', '*');

  try {
      const token = JSON.parse(request.body).token;
      const decodedToken = await admin.auth().verifyIdToken(token);
      const customToken = await admin.auth().createCustomToken(decodedToken.uid);
      response.send(customToken);
  } catch(e) {
      console.log(e);
      response.sendStatus(500);
  }
});
krupesh Anadkat
  • 1,932
  • 1
  • 20
  • 31
Martin Cremer
  • 5,191
  • 2
  • 32
  • 38
  • In my case the custom token expires after one hour, what did you do to solve that? – realappie Aug 04 '19 at 17:44
  • I just read your answer again, I guess what I need to persist is the refresh token and not the custom token. You are continuously creating custom tokens. – realappie Aug 05 '19 at 15:20
  • Yes, you should store the refresh-token, like: firebaseApp.auth().onAuthStateChanged((user) => { if(user) { fs.writeFileSync('./refresh_token.txt', refreshToken); } }); – Martin Cremer Aug 05 '19 at 15:47
  • 1
    but the refresh token changes from one user to the other, how do you save it in a text file? – Ayyash Aug 28 '19 at 05:30
  • is this safe to the public to create custom tokens from refresh tokens? – Michael Xu Mar 17 '21 at 20:59
  • to avoid any confliction you should replace 'const res = await requestP.post' with 'const anotherRes = await requestP.post' if you have another res inside the same function – Shalabyer Apr 07 '22 at 22:11
2
// Create a callback which logs the current auth state
function authDataCallback(authData) {
  if (authData) {
    console.log("User " + authData['uid'] + " is logged with token" + authData['ie']);
  } else {
    console.log("User is logged out");
  }
}
// Register the callback to be fired every time auth state changes
var ref = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com");
ref.onAuth(authDataCallback);

Event onAuth will be called on page refresh, if user was logged out authData will be null, else not. You can find token in authdata['ie']. In the screenshot bellow I have printed the token after auth and authdata object, how you can see authData['ie'] and token are similar.

authdata.ie and token

Nikita
  • 1,019
  • 2
  • 15
  • 39