13

We are using firebase with google authentication. We chose Google because our application makes Google API calls. We authorize these api calls with the access_token included in authorization payload that is returned from firebase. However, we are having trouble figuring out how to refresh the access_token after it expires. According to Google, we should assume the access_token may expire for various reasons.

Therefore, (as I understand it) we need a way to refresh this token without forcing the user to reauthorize. Ideally, I could request the offline access_type when requesting the firebase auth...but I dont see how to do that (short of triggering firebase.authWithOAuthPopup(...) again, which we absolutely do not want to do as the users session is obviously still valid.

Is it possible to get an offline access_type Google oauth token through Firebase so that Google will return a refresh_token (https://developers.google.com/accounts/docs/OAuth2WebServer#formingtheurl)? With a refresh_token, I think I can grab a new access_token for api calls.

I was trying this but its definitely not supported:

this.firebase.authWithOAuthPopup("google", this.authenticateGoogle.bind(this), {
    access_type: 'offline', <-- not passed to Google
    scope: 'https://www.googleapis.com/auth/userinfo.profile, https://www.googleapis.com/auth/devstorage.read_write'
});

All calls to https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=abcd show the access_type as online.

Thanks

hubbardr
  • 3,153
  • 1
  • 21
  • 27
  • 3
    Unfortunately, it is not currently possible to get a Google OAuth refresh token via Firebase, though it's something we're aware of and hope to fix. – Rob DiMarco Jan 12 '15 at 03:09
  • 2
    Since this answers the question, it should probably be an answer. – Kato Jan 12 '15 at 17:47

5 Answers5

8

A solution that minimizes server side implementation requirements.

TL:DR; Use the Google Sign-In for Websites library to generate the auth credentials. Login Firebase using the auth credentials, and post the offline access exchange code to your server.

Client Side

Client side I have implemented Google Sign-In for Websites by including the following :

<script src="https://apis.google.com/js/platform.js?onload=loadAuth2" async defer></script>
<script>
  function loadAuth2 () {
    gapi.load('auth2', function() {
      gapi.auth2.init({
        client_id: 'your firebase Web client ID',
        cookie_policy: 'single_host_origin',
        scope: 'profile ...'
      });
    });
  }
</script>

Note: Scope should be a space delimited list of the access scopes you require.

Assuming Firebase is loaded my login click handler is :

<script>
  function login() {
    const auth = gapi.auth2.getAuthInstance();
    auth.then(() => {
      auth.grantOfflineAccess({
        'redirect_uri': 'postmessage',
        'prompt': 'concent',
        'approval_prompt': 'force',
      }).then(offlineAccessExchangeCode => {
        // send offline access exchange code to server ...

        const authResp = auth.currentUser.get().getAuthResponse();
        const credential = firebase.auth.GoogleAuthProvider.credential(authResp.id_token);
        return firebase.auth().signInWithCredential(credential);
      }).then(user => {
        // do the thing kid! 
      });
    });
  }
</script>

Calling auth.grantOfflineAccess with 'redirect_uri': 'postmessage' causes the Google auth2 library to communicate the authentication credentials back to your web app via window.postMessage. See here for the auth2 library reference.

Elsewhere in my application I am listening for Firebase auth state to change.

firebase.auth().onAuthStateChanged(user => {
  if (user) {
    // navigate to logged in state
  } else {
    // navigate to login page
  }
});

Server Side

I POST the offlineAccessExchangeCode (which looks like {"code": "..."}) to my server to exchange for a creds for the currently authenticated user, which includes a refresh token. Though client side you can access firebase.auth().currentUser.refreshToken this token was not working for me (maybe someone can tell me I was mistaken here :D)

My server side code in Python follows. Please note that the Google SDKs are auto-generated for most Google services, so the following code should translate easily into to any language they support.

from oauth2client import client
// ...
// assuming flask
@app.route("/google/auth/exchange", methods=['POST'])
def google_auth_exchange():
    auth_code = request.get_json()['code']
    credentials = client.credentials_from_clientsecrets_and_code(
        'config/client_secret.json', ['profile', '...'], auth_code)

    print(credentials.refresh_token)

And that's pretty much it. I would assume that you have a server or some server side code if you require offline access so hopefully implementing a route isn't too far from an ideal solution.

Sequencing

Note : The GCLID Resolver is a project I am currently working on that required this.

Dawson
  • 4,391
  • 2
  • 24
  • 33
6

SOLVED for now. According to Rob DiMarco from Firebase: "Unfortunately, it is not currently possible to get a Google OAuth refresh token via Firebase, though it's something we're aware of and hope to fix."

Community
  • 1
  • 1
hubbardr
  • 3,153
  • 1
  • 21
  • 27
  • Any updates? I really need this feature for my application. I'm still confused about why Firebase would do this. It seems like a very ignorant decision. – Hudson Kim Aug 16 '20 at 06:17
2

2023 Update: This is now possible! If you follow the instructions here:

https://firebase.google.com/docs/auth/extend-with-blocking-functions#accessing_a_users_identity_provider_oauth_credentials

To create a blocking function, you can get a refresh token. See example code below:

exports.beforeCreate = authClient.functions().beforeCreateHandler((user, context) => {
  if (context.credential &&
      context.credential.providerId === 'google.com') {
    const refreshToken = context.credential.refreshToken;
    const uid = user.uid;
    // These will only be returned if refresh tokens credentials are included
    // (enabled by Cloud console).
    
    // TODO: Store or use your refreshToken here!
  }
});

Just make sure you register the blocking function after you've deployed it and make sure you select refreshToken :)

Settings

Credit: https://stackoverflow.com/a/74989323

frg100
  • 139
  • 1
  • 2
  • 9
1

Use a different OAuth 2.0 library in your client code that is able to send an authorization request with the access_type=offline. There's nothing that is firebase specific in the OAuth 2.0 interaction with Google that gets you an access token and a refresh token, so you could rely on separate code for that part. Of course you'll need to provide scope(s) specifically for Firebase (I believe at least "https://www.googleapis.com/auth/freebase") but that's not a problem for any OAuth 2.0 client library.

Hans Z.
  • 50,496
  • 12
  • 102
  • 115
  • So I've been thinking some about this approach. Without the refresh token, I have no way of using the existing oauth token from google. So I would have to make a new oauth request...which would be another login potentially. That isnt ideall. So some disadvantages to this approach is that a) I have to roll my own "custom" auth to firebase and b) that requires a server-side component to generate the JWT. Then c) I have to implement auth in my ember app and wire it up with google (although torii (http://vestorly.github.io/torii/) looks like it might work great for this). – hubbardr Mar 23 '15 at 18:59
1

Solved: Google OAuth Refresh Tokens not returning Valid Access Tokens

You have to handle authentication on a server, then return an idtoken to the client and sign in with firebase after being authenticated on the server. That way you can get refresh tokens on the backend, store them on the user on your database (from the server) and use that refresh token to reauthenticate.