How can I create long-lived tokens with Firebase Node.js SDK - firebase

I'm trying to implement the Authorization Code Flow to link actions on google with my firebase users:
https://developers.google.com/actions/identity/oauth2-code-flow
So far I've understood the flow as follows:
1 - User access the application and is redirected to the authorization url endpoint
2 - User signs in and google receives an authorization token
3 - Google sends this authorization token to the token url endpoint and gets an access token a refresh_token and a expiration time
4 - Google sends the refresh token to get a new access token when the access token is going to expire and gets a new acess token and a new expiration time
Did I get everything right?
As authorization token and access token I'm using the custom tokens from Firebase. My question is, how can I implement the refresh token? I cannot get this token from the Firebase Node.js SDK server side.
How can I greate long-lived tokens with Firebase Node.js SDK?
Is there another approach?

Yes, you got the OAuth2 process right.
The Firebase Admin SDK lets you sign the user in to your Firebase service using generated custom tokens. Though the custom token expires within 1 hour, once user is signed-in, they should be authenticated indefinitely (i.e. until user signs out). As such, there is really no need for SDK to generate refresh token.
I'd suggest a different approach. Use Actions on Google's SignIn helper intent to get user's info, such as email, name etc. Using this info, you will be able to sign the user in to Firebase as follows (referenced from the "Create Custom Token" Firebase doc):
var uid = "some-uid";
admin.auth().createCustomToken(uid)
// token == custom token
.then(function(token) {
firebase.auth().signInWithCustomToken(token).catch(function(error)
{
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
// ...
});
})
.catch(function(error) {
console.log("Error creating custom token:", error);
});
References:
"How to use refresh token?" from Firebase's GitHub
"Create custom tokens" from Firebase's docs
"Request Signin helper" from Actions on Google docs

Related

How do I refresh the Firebase Provider Access Token?

Is there a Firebase method to access and refresh the OAuth Provider token, similar to the Firebase ID Token?
I'm using Firebase Auth with Microsoft as my provider. Firebase returns two tokens:
Provider Access Token: this is the Microsoft token that I can use to make calls to Microsoft APIs
Firebase ID Token: this is the Firebase Auth token that I can use to access Google resources
I'm using signInWithRedirect and getRedirectResult to access these tokens:
signInWithRedirect(authInstance, provider)
# ...later in the file handle redirect
const result = await getRedirectResult(authInstance)
if (result) {
const credential = OAuthProvider.credentialFromResult(result)
const accessToken = credential?.accessToken
const idToken = credential?.idToken
}
There is an easy method to access the Firebase ID token that will automatically refresh it if expires, for example: authInstance.currentUser.getIdToken(). I'm having to set the accessToken in a cookie because there doesn't appear to be a method to retrieve it from Firebase. After one hour, the accessToken expires and I need to refresh it.
I can't find an easy way to retrieve and refresh the Provider Access Token outside of the getRedirectResult(). The OAuthProvider.credentialFromResult() method receives a UserCredential type that only seems to be returned after logging in.
How have others solved this issue -- how do you access and refresh the Provider Token in a place after the initial login?

Incremental authorization with Firebase and GoogleAuthProvider

I'm using Firebase v8 with the GoogleAuthProvider.
Firebase documentation provides the following code to authenticate the user.
firebase.auth().signInWithPopup(provider).then((result) => {
/** #type {firebase.auth.OAuthCredential} */
var credential = result.credential;
// This gives you a Google Access Token. You can use it to access the Google API.
var token = credential.accessToken;
// The signed-in user info.
var user = result.user;
// ...
})
Questions
Google's Using OAuth 2.0 to Access Google APIs article recommends incremental authorization (it's not Firebase, but the recommendation is clear)
It is generally a best practice to request scopes incrementally, at
the time access is required, rather than up front. For example, an app
that wants to support saving an event to a calendar should not request
Google Calendar access until the user presses the "Add to Calendar"
button.
AFAICT, there is no way to achieve incremental authorization with Firebase without re-authenticating the user. While scopes can be added to GoogleAuthProvider using addScope, a subsequent call to signInWithPopup is required (i.e. the user is re-authenticated). Is there any way to prompt only for authorization (e.g. Drive access) without re-authenticating?
Assuming the access token is short lived, can the Google ID token be used to obtain a new access token? Is re-authenticating the user the only way to obtain a new access token?
Is there a way to determine whether the access token has expired?

Get Google access token

To get Google access token after firebase auth login, I know I can simply do this:
firebase.auth().signInWithPopup(provider).then(function(result) {
var token = result.credential.accessToken;
}
but what if the user is already authenticated and I need the token? is there any way to extract it from the Firebase auth?
I've been through every value of authState but I couldn't find the google access token I've been looking for.
You can't get the access token from the onAuthStateChanged listener or the currentUser. You can only get it immediately after authentication when calling signInWithPopup, reauthenticateWithPopup, linkWithPopup, getRedirectResult, etc. Firebase Auth does not manage OAuth tokens for users. If you feel strongly about this feature, please file a feature request for it on the Firebase forum: https://groups.google.com/forum/#!forum/firebase-talk
You can also just use the GApi library to get the Google access token and pass it to Firebase to sign-in via signInWithCredential. The advantage here is that GApi will manage that OAuth token for you.

Authenticate with signInWithCredential()

I'm trying to connect to the second Firebase app and authenticate with signInWithCredential(), but I don't know how to get valid idToken for the second app:
connect(accessToken: string, config: FirebaseAppConfig) {
let one: firebase.app.App = this.angularFireTwo.database["fbApp"];
one.auth().currentUser.getToken()
.then(idToken => firebase.auth.GoogleAuthProvider.credential(idToken, accessToken))
.then(credential => {
let two = firebase.initializeApp(config, `[${config.apiKey}]`);
return two.auth().signInWithCredential(credential);
})
.catch(console.warn)
.then(console.info);
}
I'm getting and error from https://www.googleapis.com/identitytoolkit/v3/:
Invalid id_token in IdP response
If I use signInWithPopup() I can authenticate and connection is working:
two.auth().signInWithPopup(new firebase.auth.GoogleAuthProvider())
Anyone knows what should I do to get valid idToken?
UPDATE:
I've been trying to figure out authentication process and, as far I understand it , it's something like this:
from config: FirebaseAppConfig firebase reads apiKey and authDomain
it contacts the servers and gets Web Client ID for enabled Google provider 123.apps.googleusercontent.com
with this Web Client ID and authDomain it contacts www.googleapis.com, which returns idToken
this idToken is then used to identify the app that's asking user for permission to access user's profile, etc.
when user agrees, callback returns user details + credential used for this authentication, which contains idToken of the web app and accessToken of the user
Now, if I use signInWithPopup() steps 2-3-4 are done in the background (popup window). I just need a way to generate idToken for the step 4, so I can use it to generate credential firebase.auth.GoogleAuthProvider.credential(idToken, accessToken) and sign-in using signInWithCredential().
I have access to everything I need to sign-in to the second app - it's; apiKey, authDomain, Web Client id 456.apps.googleusercontent.com, and user's unique accessToken.
But still can't figure out how to do it. I tried white-listing apps' one and two Web client IDs in their auth configurations, hoping that will allow them to accept each others idTokens, but that didn't work...
When you call:
firebase.auth.GoogleAuthProvider.credential(idToken, accessToken))
The first parameter should be a Google OAuth Id token. You are using the Firebase Id token and that is why you getting the error. Besides, if you are already logged in, why are you logging in again with signInWithCredential?
If you need to sign in with a Google credential you need either a Google OAuth Id token or a Google OAuth access token.
To duplicate Firebase OAuth sign-in state from one app to another, you get the credential from signInWithPopup result and use it to signInWithCredential in the second instance.
two.auth().signInWithPopup(new firebase.auth.GoogleAuthProvider())
.then(function(result) {
return one.auth().signInWithCredential(result.credential);
});

Does Firebase support validating a pre-existing facebook access token?

Suppose, for the sake of argument, that I already have a facebook access token for a user of my application. In that case, I don't really need to go through Firebase's whole auth.login("facebook") process, I really just want a trusted server to make sure this is a real access token (e.g. by making a GET request to "https://graph.facebook.com/me" with it) and then to set the Firebase user ID appropriately. Can Firebase do this?
Firebase Simple Login was recently updated to support logging in with an existing Facebook access token.
This means that you can integrate directly with then native Facebook JS SDK in your application, and then pass that Facebook access token to Firebase Simple Login (skipping a second pop-up) via:
var ref = new Firebase(...);
var auth = new FirebaseSimpleLogin(ref, function(error, user) { ... });
auth.login('facebook', { access_token: '<ACCESS_TOKEN>' });
See the access_token option on https://www.firebase.com/docs/security/simple-login-facebook.html for more information.

Resources