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

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.

Related

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?

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

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

What format is the Google Picker API auth token?

If I intercept an access_token from an example on this page:
https://developers.google.com/picker/docs/
...it looks like this:
ya29.Glx7BW_OsFJ1CSjJ_kdt9iZixJAZHjvllMtJO09EccpURJPbCXudNP6teBz6HZ0T_ioaQLNna323UPpBQN-N0aCLWXfDVCvq4xXkbi5kKQhfwS0xakcsrNmzD5B8OA
If I paste that into here:
https://jwt.io/
...I'm told it's malformed. However, if I paste a Firebase auth token into there, it's well-formed and you can see its payload.
Ultimately, I'm trying to use the Firebase auth token in the Google Picker API, but I can't figure out how to translate it.
I was using firebase.auth().currentUser.getIdToken, but it looks like firebase.auth().getRedirectResult() gets an auth token that is the same format as the Picker API.
Google API access is scope based https://developers.google.com/identity/protocols/googlescopes
You must specify the scopes needed when requesting an access token.
Each access token you receive back, is limited to the scope and application from its original request. They are not inter-changeable.
You will need to setup a specific Google Picker API request, in order to get the access token you require https://developers.google.com/identity/protocols/OAuth2
Hope this helps
"ya29.*" token is Google's access token and it is not a jwt (it is opaque for the api callers).
Yes firebase allows you to add scopes during the sign-in process. After that you get an id token that says who the user is and a separate access token that you can use to access Google APIs.
If you look at https://firebase.google.com/docs/auth/web/google-signin you will see this:
Then, you can also retrieve the Google provider's OAuth token by calling getRedirectResult when your page loads:
firebase.auth().getRedirectResult().then(function(result) {
if (result.credential) {
// This gives you a Google Access Token. You can use it to access the Google API.
var token = result.credential.accessToken;
// ...
}
// The signed-in user info.
var user = result.user;

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);
});

Firebase Google Auth offline access_type in order to get a token refresh

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
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.
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."
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.
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.
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 :)
Credit: https://stackoverflow.com/a/74989323

Resources