Tutorial that enables the use of a Firebase Tokens to authenticate to Google Cloud APIs.
If you use Firebase
, the authentication token you recieve is not directly usable with generic GCP APIs. While there are exceptions (eg, Firebase Storage == Cloud Storage, Firebase Functions == Cloud Functions, and ofcourse Firebase Realtime Database == Cloud Datastore (native mode)), you cannot use that token directly with any other GCP service since GCP uses its own oauth2 token.
However, GCP now support OIDC Federation (see Understanding workload identity federation). What that allows you to do is use GCP’s Security Token Service (STS)
to exchange an OIDC token for one that is usable against several GCP services.
Basically
Cloud PubSub API
)for more information, see
To note, GCP workload federation exchanges the users OIDC, Azure, AWS token for a federated token. The federated token is then used to impersonate a service account. The impersonated service accounts token is then used to access the GCP service.
This tutorial does not involve this second impersonated change….I mean…it can, but I decided not to include it and instead used a service (pubsub) that accepts OIDC Without Impersonation. If you intend to use the exchanged token for any service that does not accept a federated token (meaning most of them), you need to use the federated token and invoke iamcredentials.generateAccessToken()
api.
You can find the source here
Please note: this is just a POC. The whole app does NOT have to much interms of session management, CSRF
, etc. However, it does demonstrate the conversion of the firebase token for a GCP one, as advertized.
Anyway, lets get on with it.
Select a project to host firebase and the workload federation configuration (these ofcourse can be in different projects)
export PROJECT_ID=`gcloud config get-value core/project`
export PROJECT_NUMBER=`gcloud projects describe $PROJECT_ID --format='value(projectNumber)'`
gcloud services enable iam.googleapis.com pubsub.googleapis.com firebase.googleapis.com
export FB_SVC_ACCOUNT=`gcloud iam service-accounts list --format="value(email)" --filter="displayName=firebase-adminsdk"`
# create a service account key
# you should not do this in production (or at all)...but here we are just taking shortcuts and i'm lazy.
# https://cloud.google.com/iam/docs/best-practices-for-managing-service-account-keys
gcloud iam service-accounts keys create svc_account.json --iam-account=$FB_SVC_ACCOUNT
Enable Identity Platform using UI
https://console.cloud.google.com/apis/api/identitytoolkit.googleapis.com
Then configure
Email/Password
(uncheck PasswordLess login)Authorized Domain
: server.yourdomain.com
(this is the “server” we will run locally)
/etc/hosts
and set 127.0.0.1 server.yourdomain.com
(we’re doing this since this tutorial is running locally)Note the configuration and API_KEY
for firebase. In my case, it looked like
<script src="https://www.gstatic.com/firebasejs/8.0/firebase.js"></script>
<script>
var config = {
apiKey: "AIzaSyCbIXal_yKpHTe-redacted",
authDomain: "fb-federated.firebaseapp.com",
};
firebase.initializeApp(config);
</script>
We’ll now create a test user with a weak password: alice@domain.com
.
This is the user which we will login with and configure federation.
cd cli
export GOOGLE_APPLICATION_CREDENTIALS=`pwd`/../svc_account.json
node create.js
This new user should now exist…you can test a ’local’ login first just to be sure
To do that, edit login.js
and set the configuration you noted above
In my case it was:
var firebaseConfig = {
apiKey: "AIzaSyCbIXal_yKpHTe-redacted",
authDomain: "fb-federated.firebaseapp.com",
};
Now login and print alice’s token
node login.js | jq -r '.user.stsTokenManager.accessToken'
If you decode the token, it may look like
{
"name": "alice",
"isadmin": "true",
"mygroups": [
"group1",
"group2"
],
"iss": "https://securetoken.google.com/fb-federated",
"aud": "fb-federated",
"auth_time": 1645449756,
"user_id": "alice@domain.com",
"sub": "alice@domain.com",
"iat": 1645449756,
"exp": 1645453356,
"email": "alice@domain.com",
"email_verified": true,
"firebase": {
"identities": {
"email": [
"alice@domain.com"
]
},
"sign_in_provider": "password"
}
}
Now configure workload federation for an OIDC provider.
Firebase just happens to act as a very basic OIDC provider:
For example, goto
https://securetoken.google.com/$PROJECT_ID/.well-known/openid-configuration
you should see a standard discovery endpoint for OIDC
gcloud iam workload-identity-pools create oidc-pool-1 \
--location="global" \
--description="OIDC Pool " \
--display-name="OIDC Pool" --project $PROJECT_ID
gcloud iam workload-identity-pools providers create-oidc oidc-provider-1 \
--workload-identity-pool="oidc-pool-1" \
--allowed-audiences=$PROJECT_ID \
--issuer-uri="https://securetoken.google.com/$PROJECT_ID/" \
--location="global" \
--attribute-mapping="google.subject=assertion.sub,attribute.isadmin=assertion.isadmin,attribute.aud=assertion.aud" \
--attribute-condition="attribute.isadmin=='true' && attribute.aud=='$PROJECT_ID'" --project $PROJECT_ID
# NOTE, in this specific tutorial, we will _not_ enable impersonation,
## eg, we will skip over
# gcloud iam service-accounts create oidc-federated
# gcloud iam service-accounts add-iam-policy-binding oidc-federated@$PROJECT_ID.iam.gserviceaccount.com \
# --role roles/iam.workloadIdentityUser \
# --member "principalSet://iam.googleapis.com/projects/$PROJECT_NUMBER/locations/global/workloadIdentityPools/oidc-pool-1/attribute.isadmin/true"
Create a Resource and IAM binding that supports federated tokens (eg iamcredentials
, gcs
, pubsub
). In this tutorial, we will use pubsub
gcloud pubsub topics create topic1
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member "principal://iam.googleapis.com/projects/$PROJECT_NUMBER/locations/global/workloadIdentityPools/oidc-pool-1/subject/alice@domain.com" \
--role roles/pubsub.viewer
Note that with IAM binding here, we are applying the federated Identity to the resource (and again, a handful resource types are currently support that format).
We’re now ready startup the app app:
Edit main.py
and set the values you have for PROJECT_ID
and PROJECT_NUMBER
PROJECT_ID="fb-federated"
PROJECT_NUMBER="508803172602"
Start the webserver
cd server
python main.py
Open up an incognito browser and goto https://server.yourdomain.com:38080/
Login as alice@domain.com
/Password1
What you should see is a couple of bit of output:
id_token
: the firebase id tokenserver_sts_token
: the GCP STS token as exchanged by the python webserverclient_sts_token
: the GCP STS token as exchanged by the client/browser issued tokenserver_pubsub_topics
: the GCP api call using the server-issued tokenclient_pubsub_topics
: the GCP API call using the client/browser issued tokenWell, thats it…you’ve now used a generic firebase token to access GCP
Note that GCP APIs support CORS which allows us to invoke the STS and target google api using this technique
curl -v -H "Origin: https://server.yourdomain.com:38080/" \
-H "Access-Control-Request-Method: POST" \
-H "host: sts.googleapis.com" -X OPTIONS https://sts.googleapis.com/v1beta/token
< HTTP/2 200
< access-control-allow-origin: https://server.yourdomain.com:38080/
< access-control-allow-methods: DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT
< access-control-max-age: 3600
curl -vk -H "Origin: https://server.yourdomain.com:38080/" \
-H "Access-Control-Request-Method: GET" -H "Access-Control-Request-Headers: Authorization" \
-H "host: pubsub.googleapis.com" -X OPTIONS https://pubsub.googleapis.com/v1/projects/$PROJECT_ID/topics
< HTTP/2 200
< access-control-allow-origin: https://server.yourdomain.com:38080/
< access-control-allow-methods: DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT
< access-control-allow-headers: Authorization
< access-control-max-age: 3600
Note, you cannot directly use a Firebase Custom Token for authentication. This is because the custom token is issued by the firebase service account and not Firebase itself.
That is, the JWT for a custom token may look like:
{
"alg": "RS256",
"typ": "JWT",
"kid": "ca8e77a7b85d8bfe42df6c1ccda73a3ef23bbde5"
}
{
"iss": "firebase-adminsdk-d4jcp@fb-federated.iam.gserviceaccount.com",
"sub": "firebase-adminsdk-d4jcp@fb-federated.iam.gserviceaccount.com",
"aud": "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
"uid": "alice@domain.com",
"iat": 1645450237,
"exp": 1645453837,
"claims": {
"premiumAccount": true,
"role": "foorole"
}
}
However, if you login using that token with signInWithCustomToken(), the resulting token can be used with this flow
This site supports webmentions. Send me a mention via this form.