Flutter: Generate access token from Google service account - firebase

I'm trying to access my Firbase Realtime Database through the REST API, which has restricted access. In python I'd generate an access token from the Service Account file google-services.json. Like this:
cred = credentials.Certificate("/PATH_TO_google-service.json")
token = cred.get_access_token().access_token
This token is then used to communicate with the Firebase DB REST API.
I'm unable to find a library or a way to do this in Flutter (or Dart library). I looked into googleapis_auth and use ServiceAccountCredentials.fromJson({...}), but I couldn't find a way to fetch the access token.
How can I fetch the access token? Is there a specific library for this?

Getting the access token on the client is relatively easy, but it requires the client to be authenticated (logged in). Once the user has logged in on the flutter app, you should be able to retrieve the accessToken from watching the auth:
FirebaseAuth.instance
.idTokenChanges()
.listen((String token) {
if (token == null) {
print('User is currently signed out!');
} else {
print('User is signed in!');
}
});
More details here.
Then you can validate the idToken on the server with the firebase admin sdk.
You shouldn't be using serviceAccounts in a client (website, mobile app) the reason being; they give unrestricted access to firebase resources, the firebase client sdk makes this very difficult anyway. So it's best to only use serviceAccounts on trusted servers that you control.

Related

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

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

Is there a way to determine if a Firebase user's UID is valid?

I am building a server route that I wish to restrict for use only by authenticated users. I plan to send a user.uid with a POST to this route, and I want to validate the UID is one that exists in Firebase. I know I can add UIDs manually in Firebase and check against this data, but is it possible to see what UIDs Firebase authentication is tracking? I think this approach would be better then checking against my own list.
The purpose for this is to ensure that these routes are not accessed with a phony UID (e.g. for malicious purposes).
Validating a UID is not enough to block malicious users: 1) the attackers could pretend to be other users by sending other user's UID, and 2) UID never changes or expires, which means there is no way to enforce the users (or attackers) to re-authenticate.
What you need is to pass the Firebase token from client app to your server, and validate the token before accepting it.
The token is securely signed by Firebase private key. No other party can issue a valid Firebase token.
The token is valid for only one hour. Firebase server will check the account status (e.g. password change event) before issuing a new token.
The token payload contains UID and audience. You should verify audience is your own application.
You can use Firebase Admin SDK or third party libraries to verify a Firebase token. See Firebase doc for details.
You can check whether a specific UID corresponds to a Firebase Authentication user in your project by using the Firebase Admin SDK on your server. From the Firebase documentation on retrieving user data:
admin.auth().getUser(uid)
.then(function(userRecord) {
// See the UserRecord reference doc for the contents of userRecord.
console.log("Successfully fetched user data:", userRecord.toJSON());
})
.catch(function(error) {
console.log("Error fetching user data:", error);
});
A UID is part of the payload when a Firebase user is authenticated to Firebase and is null when a user is not authenticated. You can get the UID upon user authentication. The syntax is different depending on what framework you are working in.
Check out the Firebase API Reference for specific syntax and examples: https://firebase.google.com/docs/reference/
create a token in your client app
private String getAuthTokenAndPost(){
mAuth.getCurrentUser().getIdToken(false).addOnCompleteListener(new OnCompleteListener<GetTokenResult>() {
#Override
public void onComplete(#NonNull Task<GetTokenResult> task) {
if(task.isSuccessful()){
String idToken = task.getResult().getToken();
sendReqToServer(idToken);
}else{
Toast.makeText(CartActivity.this, "couldn't generate Token", Toast.LENGTH_SHORT).show();
}
}
});
return "";
}
then use firebase admin SDK on your server side, here is an example of a Node server
getAuth()
.verifyIdToken(idToken)
.then((decodedToken) => {
const uid = decodedToken.uid;
// ...
})
.catch((error) => {
// Handle error
});
ID token verification requires a project ID. The Firebase Admin SDK attempts to obtain a project ID via one of the following methods:
If the SDK was initialized with an explicit projectId app option, the SDK uses the value of that option.
If the SDK was initialized with service account credentials, the SDK uses the project_id field of the service account JSON object.
If the GOOGLE_CLOUD_PROJECT environment variable is set, the SDK uses its value as the project ID. This environment variable is available for code running on Google infrastructure such as App Engine and Compute Engine.
check the document

Firebase 3.0 Tokens : [Error: Firebase Auth ID token has no "kid" claim]

I'm currently developing a node.js service with firebase 3.0 that is called by a web application that uses firebase 2.4.
I'm sending the current user Firebase ID token (Auth.$getAuth().token) in my header call and trying to validade this token with
var idToken = req.headers["x-access-token"];
auth.verifyIdToken(idToken).then(function(decodedToken) {
var uid = decodedToken.sub;
console.log(decodedToken);
}, function(error){
console.log(error);
});
But I'm getting:
[Error: Firebase Auth ID token has no "kid" claim]
getAuth():
UPDATE
I'have just tested generating and validating the token on the server side and I'm getting the same problem.
var auth = firebase.auth();
var token = auth.createCustomToken(userId, {"premium_account": true});
console.log(token);
auth.verifyIdToken(token).then(function(decodedToken) {
console.log(decodedToken);
}, function(error){
console.log(error);
});
Any suggestions?
UPDATE 2: [SOLUTION]
The problem in my case was that the Tokens generated with AngularFire 2.X.X are not compatible with the Firebase 3.X.X that is running in my server. So after digging into some thoughts that people wrote here and in this google group topic the workaround was to use jsonwebtoken as follows:
var jwt = require('jsonwebtoken');
jwt.verify(idToken, fbKey, function(err, decoded) {
if (!err){ console.log(decoded); }
});
You can find the fbKey accessing the new firebase console and going into Settings -> Project Settings -> Database.
The documentation states that a Firebase ID Token is not the same as a Custom Token, and that verifyIdToken() is not intended for verifying tokens generated with generateCustomToken().
Old style custom tokens still seem to work (signed with a database secret instead of a service account private key). You can generate and verify these yourself using firebase-token-generator.js and/or jsonwebtoken.js.
Copied from Firebase Project > Settings > Database > Secrets
Create custom database authentication tokens using a legacy Firebase
token generator. At least one secret must exist at all times.
Seems like there is no way to use firebase's createCustomToken and verifyIdToken in a couple.
Method createCustomToken uses method sign from jsonwebtoken module which does not put "kid" claim in header section of jwt by default.
And createCustomToken does not put it itself.
I suppose at this time you can use jsonwebtoken module directly to generate token with own key id.
firebase#3.0.2
--jsonwebtoken#5.7.0
The token you pass to your server is not a JWT token, and verifyIdToken need a JWT Token.
To obtain a JWT token in your web application, run firebase.app().auth().currentUser.getToken().
Personnally I removed angularfire, the basic firebase framework made angularfire pretty useless in my opinion. Plus, a compatible version with firebase 3.0 of angularfire is not yet released, probably in the next week according to the firebase team.

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