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

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

Related

Not sure where to request Google Contacts API scopes when using Auth0 + Next.js

I am trying to get my app verified by Google for both of Google Contacts readonly scopes so users can import their contacts into my app.
https://www.googleapis.com/auth/contacts.readonly
https://www.googleapis.com/auth/contacts.other.readonly
I am using Auth0 for handling users in my Next.js app. The package I am using is #auth0/nextjs-auth0. I have initialized auth0 as the documentation suggests.
// pages/api/auth/[...auth0].ts
import {handleAuth} from '#auth0/nextjs-auth0';
export default handleAuth();
In the Google Social connection in Auth0, I have checked the Contacts box and when I login with Google, I see the expected request to allow access to my contacts.
Using the googleapis package, I then call the google.people.connections.list method (which to my understanding is enabled by the contacts.readonly scope) with the Google user's access token and receive back a list of people from my main contacts list, NOT the "other" contacts list.
// This is the abstract Google API service config where the user's access token is set
export interface GoogleServiceConfig {
accessToken: string
}
abstract class AbstractGoogleService {
protected _config: GoogleServiceConfig
constructor(config: GoogleServiceConfig) {
this._config = config
}
}
export default AbstractGoogleService
// This is the Google Contacts Helper service that extends the above Abstract Service
export default class GoogleContactsService extends AbstractGoogleService {
// Search for contacts in user's google account and return them as options
async search(options?: any) {
const service = google.people({ version: 'v1', headers: {
// the accessToken is the one fetched from the Google IDP profile provided by the Auth0 management API
authorization: `Bearer ${this._config.accessToken}`
}})
return service.people.connections.list({
resourceName: 'people/me',
pageSize: 100,
personFields: 'names,emailAddresses'
})
}
}
However, when I submitted my request for app scope verification to Google, they said I was only requesting the https://www.googleapis.com/auth/contacts.other.readonly scope and needed to revise either my scope verification request or my code.
I'm very confused as to where I'm supposed to request the two different Google Contact scopes as it seems that checking the Contacts box in the Auth0 Social Google configuration does request the contacts.readonly scope.
I tried putting the two Google Scopes in my handleAuth() call in [...auth0].ts as shown here https://github.com/auth0/nextjs-auth0/blob/main/EXAMPLES.md#access-an-external-api-from-an-api-route but received an error when logging in of...
access_denied (Service not found: https://www.googleapis.com/auth/contacts.readonly https://www.googleapis.com/auth/contacts.other.readonly)
This is my first time integrating with Auth0 and Google APIs so any help would be much appreciated as I am going in circles now and having troubling making heads or tails.
Thanks!
I was finally able to get approval from Google and wanted to share for the rest of the Googlers out there.
To get approval when checking the Contacts box in Auth0:
I needed to request this scope from Google:
https://www.googleapis.com/auth/contacts
This then let me get verified by Google and call the People API.

Storing and using the Firebase Auth Token for API calls to my server

I implemented Firebase Phone Auth for SignIn in my ReactNative project. Now I want to use this JWTToken to be passed to the API calls that I make to my server.
And at the server side, I would be validating the token which was passed through the API calls and respond with the proper response. My question is, how can I pass this token in the API calls that I make to my server?
I can store the token (within my first loading screen of the app, where it authenticates the User) in the localStorage and fetch it later in any of my screens to make the API calls
I can access the Token directly my importing the firebase package in each and every screen (from which am planning to do the API calls) like this: https://rnfirebase.io/reference/auth/idtokenresult and pass it in the API calls
But I was thinking about storing the Token (fetched during the loading screen) in a global variable inside my ReactNative project and that can be accessed from any screens. But I couldn't find how this can be done? Or which one would be the more appropriate way to do this?
EDIT:
This is how am getting the Token :
auth().onIdTokenChanged(function(user) {
if (user) {
user.getIdToken().then( token => {
console.log( token )
});
}
});
Storing the token in local storage is not going to work out well for you in the long run. ID tokens expire after 1 hour, and will not successfully verify on the server after that.
Each individual page should set up an ID token listener so it can use the most fresh token provided by the Firebase Auth SDK. The SDK will automatically refresh it and provide you with the latest token in the callback. Every time the token changes, you should use that value in your API calls. Use onIdTokenChanged():
firebase.auth().onIdTokenChanged(function(user) {
if (user) {
// User is signed in or token was refreshed.
}
});

Can I use Firebase's authentication to protect data on my own private server?

Due to some constraints of my project I need to manage my api server and database separately from firebase, however there is nothing stoping me from using their other tools like analytics and in this particular case authentication.
I wanted to ask if following scenario is possible:
I authenticate user on my client app using their SDK's
I send relevant jwt data in headers of my API
Can I then somehow validate this on my server, so in essence avoid complex auth service and use firebase for this instead? Are there any examples, preferably for NodeJS server.
Yes you can, it's very common use case and firebase auth has been made with such usage in mind. As you said, send jwt data on headers, then on the server, you verify the token with firebase service:
var admin = require('firebase-admin');
admin.initializeApp(); //may require other steps, see last link
// idToken comes from the client app
admin.auth().verifyIdToken(idToken, true)//second argument is optional, checks whether the ID token was revoked
.then(function(decodedToken) {
let uid = decodedToken.uid;
// ...
}).catch(function(error) {
// Handle error
});
https://firebase.google.com/docs/auth/admin/verify-id-tokens
https://firebase.google.com/docs/reference/admin/node/admin.auth.Auth#verifyidtoken
https://firebase.google.com/docs/admin/setup

Can we use firebase with flutter just for authentication and custom server for everything else?

I an planning to make a social media application using flutter. I want to give the user ability to sign in using Google or Facebook accounts which can be easily done using firebase authentication but I am worried about the cost because within 2 months the number of users will be approximately 100,000. I was thinking of we could just sign up/sign in using firebase and store data and push/pull all the other requests from a hostgator server(mysql db).
And if it is possible can I do it using just dart language or do I need some other languages too like ruby, C# it python (I am not a big fan of php)
Yes, you can use Firebase just for authentication and your mysql db on Hostgator for everything else. Firebase auth is free (except to phone auth), as #Doug Stevenson has mentioned.
The (firebase) authentication can be done in your Flutter app. You do not need a server code to do this. Firebase auth gives back 'user' object with attributes like user name, email, profile photo url etc which you can store in your mySQL db as well (if required).
Firebase Authentication doesn't cost anything to use for signing in with Google or Facebook accounts. It doesn't cost any more based on the number of users you have. You are only going to be charged if you use phone authentication past the initial free tier limits, as described on the pricing page. So I wouldn't bee too concerned about costs.
You can do all the signups in client app code. You don't need a backend at all to use Firebase Auth, but you can bring any backend you want.
import 'package:express/express.dart';
import 'package:firebase_admin/firebase_admin.dart';
void main() async {
// Initialize the Firebase Admin SDK
await FirebaseAdmin.initializeApp(
credential: FirebaseAdmin.ServiceAccountCredential(
'/path/to/serviceAccountKey.json'));
// Create a new Express app
var app = Express();
// Implement the API endpoint for authentication
app.post('/login', (request, response) async {
// Get the user's email and password from the request body
var email = request.body['email'];
var password = request.body['password'];
// Verify the user's credentials using the Firebase Admin SDK
try {
var userRecord = await FirebaseAuth.instance
.signInWithEmailAndPassword(email: email, password: password);
// If the login was successful, create a new session for the user
request.session['uid'] = userRecord.uid;
// Return a success response
response.send({'success': true});
} catch (error) {
// If the login failed, return an error response
response.send({'success': false, 'error': error.toString()});
}
});
// Start the server
app.listen(3000);
}

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