I'm trying to create a wrapper REST API for Firebase Authentication using cloud functions.
How can I create user or authenticate user on Firebase once I have the Facebook Access token on client (using Facebook SDK)?
If you are using Firebase Functions with HTTP triggers, you can use firebase.js client node.js library to authenticate a user and return the Firbease tokens in your REST API. You would send the Facebook Access token to that HTTP endpoint, sign in the user with signInWithCredential using node.js client library and return the ID token and refresh token.
If you want to use REST API:
curl 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAssertion?key=[API_KEY]' \
-H 'Content-Type: application/json' \
--data-binary '{"postBody":"access_token=[FACEBOOK_ACCESS_TOKEN]&providerId=[facebook.com]","requestUri":"[http://localhost]","returnIdpCredential":true,"returnSecureToken":true}'
This would return the Firebase ID token and refresh token:
{
"idToken": "[ID_TOKEN]",
"refreshToken": "[REFRESH_TOKEN]",
...
}
This is all you need for a Firebase Auth session.
To construct the user, call the following API with the ID token:
curl 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/getAccountInfo?key=[API_KEY]' \
-H 'Content-Type: application/json' --data-binary '{"idToken":"[FIREBASE_ID_TOKEN]"}'
This would return the user and the data associated:
{
"kind": "identitytoolkit#GetAccountInfoResponse",
"users": [
{
"localId": "ZY1rJK0...",
"email": "user#example.com",
"emailVerified": false,
"displayName": "John Doe",
"providerUserInfo": [
{
"providerId": "password",
"displayName": "John Doe",
"photoUrl": "http://localhost:8080/img1234567890/photo.png",
"federatedId": "user#example.com",
"email": "user#example.com",
"rawId": "user#example.com",
"screenName": "user#example.com"
}
],
"photoUrl": "https://lh5.googleusercontent.com/.../photo.jpg",
"passwordHash": "...",
"passwordUpdatedAt": 1.484124177E12,
"validSince": "1484124177",
"disabled": false,
"lastLoginAt": "1484628946000",
"createdAt": "1484124142000",
"customAuth": false
}
]
}
To refresh the ID token after it expires, use the refresh token returned:
With REST API:
curl 'https://securetoken.googleapis.com/v1/token?key=[API_KEY]' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data 'grant_type=refresh_token&refresh_token=[REFRESH_TOKEN]'
This would return a new ID token and refresh token:
{
"expires_in": "3600",
"token_type": "Bearer",
"refresh_token": "[REFRESH_TOKEN]",
"id_token": "[ID_TOKEN]",
"user_id": "tRcfmLH7o2XrNELi...",
"project_id": "1234567890"
}
To use this with client library on the backend:
var firebase = require('firebase');
You send the FB access token from the client to your HTTP endpoint and sign-in with it:
var cred = firebase.auth.FacebookAuthProvider.credential(fbAccessToken);
firebase.auth().signInWithCredential(cred).then(function(user) {
// User is obtained here.
// To get refresh token:
// user.refreshToken
// To get ID token:
return user.getIdToken().then(function(idToken) {
// ...
})
}).catch(function(error) {
});
Related
In our project we use Auth0 with Google login and don't use username/password login. We started to write tests for our app, but we can't login in Cypress framework. We need login using Google auth (but it's not possible to do it using request). Also, we don't store token in localStorage, because frontend part uses auth0 react sdk for it.
I tried to find how to login in Cypress using auth0, but found only solutions for username/password login.
Possible solution from this post:
Cypress.Commands.add("login", () => {
cy.clearLocalStorage();
const email = "";
const password = "";
const client_id = "";
const client_secret = "";
const audience = "";
const scope = "";
cy.request({
method: "POST",
url: "",
body: {
grant_type: "password",
username: email,
password,
audience,
scope,
client_id,
client_secret,
},
}).then(({ body: { access_token, expires_in, id_token, token_type } }) => {
cy.window().then((win) => {
win.localStorage.setItem(
`##auth0spajs##::${client_id}::${audience}::${scope}`,
JSON.stringify({
body: {
client_id,
access_token,
id_token,
scope,
expires_in,
token_type,
decodedToken: {
user: JSON.parse(
Buffer.from(id_token.split(".")[1], "base64").toString("ascii")
),
},
audience,
},
expiresAt: Math.floor(Date.now() / 1000) + expires_in,
})
);
cy.reload();
});
});
});
I can get token using client_credentials grant type, but I can't use it in this solution, because it uses id_token instead of the access_token.
Is it possible to use client_credentials grant type for this login? Or should we turn ON username/password login for it?
Request for client_credentials grant type:
curl --request POST \
--url 'https://YOUR_DOMAIN/oauth/token' \
--header 'content-type: application/x-www-form-urlencoded' \
--data grant_type=client_credentials \
--data client_id=YOUR_CLIENT_ID \
--data client_secret=YOUR_CLIENT_SECRET \
--data audience=YOUR_API_IDENTIFIER
It returns:
{
"access_token":"eyJz93a...k4laUWw",
"token_type":"Bearer",
"expires_in":86400
}
Here's a Cypress test that shows how we handle Keycloak, Okta, and Auth0 in Ionic for JHipster:
https://github.com/jhipster/generator-jhipster-ionic/blob/main/generators/ionic/resources/oauth2/cypress/support/commands.ts
We turned ON username/password way for login, and used UI for login. After the first login, Cypress don't show login page again, because we are already logged.
My solution (we use only username/password without additional info from Auth0):
Cypress.Commands.add('loginAuth0', (username = Cypress.env('auth0_username'), password = Cypress.env('auth0_password')) => {
cy.visit(`/`);
cy.wait(4000); // wait when auth0 modal is loaded (if user is not logged in)
cy.document().then(doc => {
if (doc.querySelector("#username") != null && doc.querySelector("#password") != null) {
cy.get('#username').type(username);
cy.get('#password').type(password);
cy.get('button[name="action"]').click();
} else {
cy.log("User is already logged.");
}
});
});
I am being able to receive and display the notification on the console but it doesn't show up in the browser (chrome).
The steps I am following are:
I start the app and get notification token with the function
FirebaseMessaging.instance.getToken().then((String token) {
debugPrint('Token: $token');
});
The token from step 1. is inserted in the script below (the 'to' field) and the request is sent to Firebase servers
#!/bin/bash
DATA='{"notification": {"body": "This is a message","title": "Marcelo"}, "priority": "high", "data": {"click_action": "FLUTTER_NOTIFICATION_CLICK", "id": "1", "status": "done"}, "to": "eMkcLmwSSBeWXS_YEB-b_R:APA91bG2pyrDVr0BBoya0rQET0vfwTVE3aTeQsoqsxVMK70ypm6aaa-pNdX9uQ5BgEsoQGuVoe-EpeePJB8Q7XUfTvrTlgtRW8HSZ3qOaxotFUSaq8JqrgRtummIOnMFYUGqtg-sMP8Y"}'
curl https://fcm.googleapis.com/fcm/send -H "Content-Type:application/json" -X POST -d "$DATA" -H "Authorization: key=AAAAKavk7EY:APA91bEtq36uuNhvGSHu8hEE-EKNr3hsgso7IvDOWCHIZ6h_8LXPLz45EC3gxHUPxKxf3254TBM1bNxBby_8xP4U0pnsRh4JjV4uo4tbdBe2sSNrzZWoqTgcCTqmk3fIn3ltiJp3HKx2"
A success response is received back
{"multicast_id":5695802004264337317,"success":1,"failure":0,"canonical_ids":0,"results":[{"message_id":"0:1615137996888514%2fd9afcdf9fd7ecd"}]}
The notification is received in the app in the form
FirebaseMessaging.onMessage.listen((RemoteMessage message)
{
RemoteNotification notification = message.notification;
if (notification != null) {
print('Title ${notification.title}');
print('Body ${notification.body}');
}
});
The browser however doesn't show the notification. What am I missing?
static onMessageWebNotification(NotificationData notificationData, String payloadData) {
html.window.navigator.serviceWorker.ready.then((registration) {
var notificationTitle = notificationData.title;
var notificationOptions = {
'body': notificationData.body,
'data': notificationData
};
registration.showNotification(
notificationTitle,
notificationOptions,
);
});
}
Final Goal: my company will start use Firestore to update transaction status to our Mobile Application and Angular 9 frontend.
Immediate challenge: learn how use Custom Token.
Current issue: I get this erro although it is owner:
"error": {
"code": 403,
"message": "Missing or insufficient permissions.",
"status": "PERMISSION_DENIED"
}
I can't understand why getting PERMISSION_DENIED if the idToken is related to the Collection Owner.
IAM Admin showing the user is owner:
Steps to reproduce:
1 - I succesfully can get a Custom Token from this code published to https://us-central1-firetestjimis.cloudfunctions.net/getCustomToken
import * as functions from 'firebase-functions';
import * as admin from "firebase-admin";
export const getCustomToken = functions.https.onRequest((request, response) => {
if (admin.apps.length < 1) { //Checks if app already initialized
admin.initializeApp();
}
const uid = "User UID copied from https://console.firebase.google.com/project/firetestjimis/authentication/users";
admin.auth().createCustomToken(uid)
.then(function (customToken) {
console.log(customToken.toString);
response.send(customToken);
})
.catch(function (error) {
console.log("Error creating custom token:", error);
});
});
2 - I can successfully post this Custom Token to https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyCustomToken and get back idTken like
{
"kind": "identitytoolkit#VerifyCustomTokenResponse",
"idToken": "eyJhbGciOiJSUzI1NiI ... .... aMorw",
"refreshToken": "AE0u-Ney9OJ04Z3xA7ACsmI1S637hXeuCTEdaEU9cxhhPnlwh-9q0X7QpSxVRIYdTdrTgXUbS9Q6yUdAWVeXzhGMiLLLHtwSWSoVSWnZs3Gp1Sb050tThNPQiSsSss8GkCigft3PTBkY4nIbRy3A5dA8FHCLbMYQSfKYqvu8To76UyMVCYONJOM",
"expiresIn": "3600",
"isNewUser": false
}
8 - Now I want to post a simple document to Firestore collection throw
curl --location --request POST 'https://firestore.googleapis.com/v1/projects/firetestjimis/databases/(default)/documents/transfer' \
--header 'Authorization: Bearer eyJhbGc ... ... iNaMorw' \
--header 'Content-Type: application/json' \
--data-raw '{
"fields": {
"id": {
"stringValue": "1"
},
"status": {
"stringValue": "fracasso"
}
}
}'
and I get the error mentioned above.
IN case it is relevant, here is the collection manually created:
*** edit
Rules:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read: if false;
allow update, write: if request.auth.uid != null;
}
}
}
Desire:
Anyone can read but only post with valid idToken resulted from custom token can create and update.
Final goal: an Angular client receives a token valid for one hour in order to query data from FireStore.
Steps to produce a prove of concept and learn how to work with Custom Tokens:
1 - I created a project in Firebase using firebase tool (https://console.firebase.google.com/project/firetestjimis/overview)
2 - I added Firestore database and created a collection. I chose production instead of test because this POC is aimed for security reasons.
3 - I added manually an user in Firebase/Authentication/Add User
4 - I copied User UID from above user added (it is used bellow)
5 - I created a very simple firebase Cloud Function applications in order to answer back a Custom Token. Basically I ran firebase init and added this code in index.tx
import * as functions from 'firebase-functions';
import * as admin from "firebase-admin";
export const getCustomToken = functions.https.onRequest((request, response) => {
if (admin.apps.length < 1) { //Checks if app already initialized
admin.initializeApp();
}
const uid = "UID mentioned above";
admin.auth().createCustomToken(uid)
.then(function (customToken) {
console.log(customToken.toString);
response.send(customToken);
})
.catch(function (error) {
console.log("Error creating custom token:", error);
});
});
I reached this by following other stackoverflow answer
6 - I succesfully can get a Custom Token from https://us-central1-firetestjimis.cloudfunctions.net/getCustomToken
7 - I can successfully post this Custom Token to https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyCustomToken and get back idTken like
{
"kind": "identitytoolkit#VerifyCustomTokenResponse",
"idToken": "eyJhbGciOiJSUzI1NiI ... .... aMorw",
"refreshToken": "AE0u-Ney9OJ04Z3xA7ACsmI1S637hXeuCTEdaEU9cxhhPnlwh-9q0X7QpSxVRIYdTdrTgXUbS9Q6yUdAWVeXzhGMiLLLHtwSWSoVSWnZs3Gp1Sb050tThNPQiSsSss8GkCigft3PTBkY4nIbRy3A5dA8FHCLbMYQSfKYqvu8To76UyMVCYONJOM",
"expiresIn": "3600",
"isNewUser": false
}
8 - Now I want to post a simple docuemnt to Firestore collection throw
curl --location --request POST 'https://firestore.googleapis.com/v1/projects/firetestjimis/databases/(default)/documents/transfer' \
--header 'Authorization: Bearer /eyJhbGc ... ... iNaMorw' \
--header 'Content-Type: application/json' \
--data-raw '{
"fields": {
"id": {
"stringValue": "1"
},
"status": {
"stringValue": "fracasso"
}
}
}'
and I get this error:
{
"error": {
"code": 401,
"message": "Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",
"status": "UNAUTHENTICATED"
}
}
So my main question is: isn't that idToken returned from https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyCustomToken a valid token to reach the related Firestore?
There is a forward slash before the ID token in the header that shouldn't be there:
--header 'Authorization: Bearer /eyJhbGc ... ... iNaMorw' \
^
I'm currently using firebase as my backend webserver , and I used the auth REST API to authenticate users in my app (login using email and password ). My question is I want to know if there is an http request that I can a response by it that includes if the email that logged in is verified or not , I have tried mixing the rest API with the firebase-Auth package but it didn't work .
I just found the "setAccountInfo" endpoint, but for it to work I need the " oobCode " from the confirmation email that was send to the user and I can't get that automatically, I guess.
Can anyone help me on this?
I think you can do that with the getAccountInfo endpoint, which returns the following payload (sample from the doc) that contains a emailVerified property:
{
"users": [
{
"localId": "ZY1rJK0...",
"email": "user#example.com",
"emailVerified": false,
"displayName": "John Doe",
"providerUserInfo": [
{
"providerId": "password",
"displayName": "John Doe",
"photoUrl": "http://localhost:8080/img1234567890/photo.png",
"federatedId": "user#example.com",
"email": "user#example.com",
"rawId": "user#example.com",
"screenName": "user#example.com"
}
],
"photoUrl": "https://lh5.googleusercontent.com/.../photo.jpg",
"passwordHash": "...",
"passwordUpdatedAt": 1.484124177E12,
"validSince": "1484124177",
"disabled": false,
"lastLoginAt": "1484628946000",
"createdAt": "1484124142000",
"customAuth": false
}
]
}
Sample Request (from the doc, pasted for reference):
curl
'https://identitytoolkit.googleapis.com/v1/accounts:lookup?key=[API_KEY]'
\
-H 'Content-Type: application/json' --data-binary '{"idToken":"[FIREBASE_ID_TOKEN]"}'
In the example above, you would replace [API_KEY] with the Web API Key
of your Firebase project and [FIREBASE_ID_TOKEN] with the user's
Firebase ID token.
PS: I've not tried the setAccountInfo endpoint you mention at the end of your question, but I think it is used for sending (POST) the verification code received by email (From the doc: -> "oobCode: The action code sent to user's email for email verification.")
import pyrebase
firebaseConfig = {
"apiKey": "",
"authDomain": "",
"projectId": "",
"storageBucket": "",
"messagingSenderId": "",
"appId": "",
"measurementId": "",
"databaseURL": ""
}
firebase = pyrebase.initialize_app(firebaseConfig)
auth = firebase.auth()
email= "your Email"
password = "your Password"
login = auth.sign_in_with_email_and_password(email, password)
acc_info=auth.get_account_info(login['idToken'])
if "users" in acc_info:
if acc_info["users"]:
for val in acc_info['users']:
if "emailVerified" in val:
print(val["emailVerified"])