Querying internal metadata server from React app hosted on Google Cloud Run - javascript

I'm implementing Google Cloud's service-to-service authentication documentation between two apps hosted on Cloud Run. A client-side React app - created using create-react-app - will be making calls to a server-side Node.js app, where the latter doesn't have unauthenticated invocation allowed.
However, I'm not able to query the internal metadata server form my React app to get an authorization token. When running the request in the sample code on that page, I get the error in the attached screenshot. Does anyone have a solution to this?
Code to make call:
const buttonClickHandler = async (event) => {
const metadataServerTokenURL = 'http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience=';
const tokenRequestOptions = {
uri: metadataServerTokenURL + process.env.REACT_APP_API_BASE_URL,
headers: {
'Metadata-Flavor': 'Google'
}
};
// Fetch the token, then provide the token in the request to the receiving service
request(tokenRequestOptions)
.then((token) => {
return request(process.env.REACT_APP_API_BASE_URL).auth(null, null, true, token)
})
.then((response) => {
console.log(response)
})
.catch((error) => {
console.error(error)
});
}
Error:
TypeError: Failed to fetch
Browser console error

This service-to-service procedure you linked in your question is, as mentioned in the name, for service to service communication.
The documentation you shared mentions this:
Note that this method does not work outside of Google Cloud, including from your local machine.
The metadata server is not intended to be called from outside Google Cloud. This is why it is not working in your browser.
If you are calling a backend container, you could:
make this backend container callable by unauthenticated users (IAM role: "roles/run.invoker", member: "allUsers")
Implement token retrieval with firebase/auth and the Identity Platform.

Related

REST API with Google Firebase Authentication & Functions using Bearer Token

Quick Background: I'm programming an API that is thought to be used "standalone" i.e. there is no frontend involved. API access should be possible directly from e.g. Postman or Curl with a Bearer token in the Authentication Header.
I was looking at Google Firebase and thought it is probably a really good fit because all of the authentication is already "builtin" and directly compatible with Google Cloud Functions.
However after a weekend of experimenting I can not seem to figure out how to implement an REST API (With Google Cloud Functions) where the User can (In an web-interface) request an API token to interact with the API.
I don't want to handle authentication myself. I really would love to use the Firebase authentication for the API.
Here is what the final process should look like:
User logs into an web-interface with the standard Firebase Authentication process.
User clicks on something like "Request API Key" and gets a key shown in the web-interface (e.g. abc...). that is generated by Firebase Authentication.
User can make requests with e.g. curl to the API Hosted in Google Cloud Functions and just has to set the Authorization Header (Bearer abc...) and the "validation" of that token is handled by Firebase Authentication.
Here is what I already tried to generate the token:
admin.auth().createCustomToken(uid)
.then(function(customToken) {
console.log(customToken);
})
.catch(function(error) {
console.log('Error creating custom token:', error);
})
And then set the Token logged to the console in Postman as Bearer Token, and then use the following function to verify the token:
const authenticate = async (req, res, next) => {
if (!req.headers.authorization || !req.headers.authorization.startsWith('Bearer ')) {
res.status(403).send('Unauthorized');
return;
}
const idToken = req.headers.authorization.split('Bearer ')[1];
try {
const decodedIdToken = await admin.auth().verifyIdToken(idToken);
req.user = decodedIdToken;
next();
return;
} catch(e) {
console.log(e);
res.status(403).send('Unauthorized');
return;
}
}
Then I get this error
message: 'verifyIdToken() expects an ID token, but was given a custom token. See https://firebase.google.com/docs/auth/admin/verify-id-tokens for details on how to retrieve an ID token.'
I understand that if I would implement an web-interface I could grab the ID token from the devtools (?), but the token is then only valid 1 hour... What I need is a token that is valid "indefinitely" and can be generated and shown to the user.
I think I know that I have to use Custom Tokens somehow but can not figure out how to get them working... (https://firebase.google.com/docs/auth/admin/create-custom-tokens).
Thanks very much in advance everybody!
Best
Rick
You're trying to build an API management solution on top of Firebase and Cloud Functions. Custom tokens and ID tokens are not suitable for this purpose. Custom tokens are only meant to be used as a user authentication credential on end user devices, and ID tokens represent a successful auth response. Both types of tokens expire after an hour.
If you need long-lived, managed API keys, then you will have to implement them yourself. There's nothing built into Firebase that you can use out of the box. I once implemented such a solution as a prototype, where I generated a Firestore document each time a user signed in and requested an API key. Then I used the document ID as the API key, which I could validate in the Cloud Function.
const apiKey = req.headers.authorization.split('Bearer ')[1];
const doc = await admin.firestore().collection('apiKeys').doc(apiKey).get();
if (doc.exists) {
next();
}
I also had to implement some local API key caching to make this work efficiently.
You might be able to avoid some of this work by using a solution like Google Cloud Endpoints (https://cloud.google.com/endpoints), although I don't have any personal experience with that. Finally, also look at open source solutions like https://wso2.com/api-management/ that enable you to set up your own API key management and gateway.

How to Get a valid access token for my API and Microsoft Graph from Azure Active Directory?

I am trying to set up a spa javascript app which logs the user into our azure active directory tenant and then retrieves profile information from microsoft graph and calls an azure function written in c# core (my API).
I have separate application registrations set up for my website and the api in azure active directory.
I'm using the MSAL.js library in the javascript spa website and I'm using the newer microsoft identity / v2.0 endpoints.
The SPA app signs into active directory as expected and is able to use the access token to make the call to graph for the profile information. In my azure function I validate the token and this fails with the error "IDX10511: Signature validation failed. Keys tried: ....."
Now if I remove Microsoft graph from the scopes when requesting a token I get a token that when passed to the azure function validates perfectly well but I can no longer retrieve profile data in the spa app?
How do I get both to work?
Its also worth noting that ive tested the tokens with jwt.io and it is also unable to verify the signature when graph is used.
Heres how I'm getting my token:
var msalConfig = {
auth: {
redirectUri: window.location.origin, // forces top level instead of specific login pages - fixes deep link issues.
clientId: "Client ID of the website app", //This is your client ID
authority:
"https://login.microsoftonline.com/my-tennant-guid" //This is your tenant info
},
cache: {
cacheLocation: "localStorage",
storeAuthStateInCookie: true
}
};
const msalUserAgent = new Msal.UserAgentApplication(msalConfig);
var requestObj = {
scopes: ["User.Read", "api://MyApi/Access"]
};
//when the spa starts up I login using redirects
msalUserAgent.loginRedirect(requestObj);
//then before calling an api I request a token using this method
acquireTokenSilent() {
var promise = msalUserAgent.acquireTokenSilent(requestObj);
return promise;
},
Try specifying the scopes as scopes: ["User.Read"] in the acquireTokenSilent() function.
Since an access token is only valid for one API.
If you need two, call acquireTokenSilent twice with different scopes.
It's okay to specify scopes for two APIs when signing in, but not when getting tokens. A token has an audience that specifies the target API. So you can't use a token for one API against another. And that's why it's only valid for one API.

Firebase FCM (Javascript): Error when request permission (Error status = 500)

I'm new to Firebase Cloud Messaging and I need to implement notifications in my webapp.
If the browser requests notification for the first time there is no error occurred and the token fetched successfuly. But if I delete the notification from the browser parametre (I use Chrome) and ask for permission again, it shows me an error in the console.
DELETE https://fcmregistrations.googleapis.com/v1/projects/teak-perigee-*****/registrations/dcVW8MdcapIy5CrSqGutkj:APA91bFoslZEsjgIk16CUfol*****************
FirebaseError: Messaging: A problem occured while unsubscribing the user from FCM: FirebaseError: Messaging: A problem occured while unsubscribing the user from FCM: Internal error encountered. (messaging/token-unsubscribe-failed). (messaging/token-unsubscribe-failed).
Actually the token is fetched even this error occurs. but in this situation I handle the new token in the catch block of the promise. This is my code when permission is fired:
askForPermissioToReceiveNotifications = () => {
const messaging = firebase.messaging();
Notification.requestPermission().then(async (permission) => {
if (permission == 'granted') {
try {
const token = await messaging.getToken();
if (token) {
console.log(token);
return token;
} else {
console.log('No Instance ID token available. Request permission to generate one.');
}
} catch (error) {
console.log('An error occurred while retrieving token. ', error);
//BUT THE NEW TOKEN SUCCESSFULY FETCHED
const token = await messaging.getToken();
if (token) {
console.log(token);
return token;
} else {
console.log('No Instance ID token available. Request permission to generate one.');
}
}
}
})
.catch(error => console.log(error));
}
I don't know if I miss something and I hope I can find a solution.
I faced a similar issue and believe this may be the explanation.
Issue was introduced in version 7.0.0 of the firebase-js-sdk. A workaround for now is to use version 6.6.2 or lower. I have filed a Github issue hereĀ for users effected to track.
So to enable use an older version just update the following in your index.html:
<script src="/__/firebase/6.6.2/firebase-app.js"></script>
<script src="/__/firebase/6.6.2/firebase-messaging.js"></script>
and change the following to your service worker file (typically named: firebase-messaging-sw.js):
importScripts('/__/firebase/6.6.2/firebase-app.js');
importScripts('/__/firebase/6.6.2/firebase-messaging.js');
it turns out that new versions of Firebase SDKs depend on a new internal infrastructure service, called FIS (the Firebase Installations Service) for targeting identifiers ("FIDs" or "Instance-IDs").
If you are using API key restrictions for the API keys you use in your application, you will have to extend those restrictions to allow usage with the new Firebase Installations Service at firebaseinstallations.googleapis.com.
To allow your API key in question to be used with the new Firebase Installations API:
go to the Google Cloud Console
choose the relevant project (i.e. the project you use for your application)
open the menu and go to APIs & Services -> Credentials
click Edit API key for the API key in question
scroll down to API restrictions
from the dropdown, choose FCM Registration API
click Save
wait a couple of minutes for Google servers to update and retry...
The Firebase library has a bug where it saves the token in an indexed database in your app. When you delete your credentials, it fails to delete the token and instead tries to fetch the old token when creating a new one, which leads to an error since the token no longer exists in Firebase. To resolve the issue, simply follow these steps.
-Open Google Chrome DevTools by pressing F12
-Click on the Application tab.
-Expand the Storage section in the left-side panel.
-Expand IndexedDB
-Expand firebase-messaging-database
-Right click on the key-value and delete it
-Refresh your app and the error is gone

IBM Watson JavaScript SDK for Speech-To-Text WebSocket Issue

I'm writing a Vue.js web app and trying to access the Watson Speech-To-Text API. I found a very simple implementation using the JavaScript SDK (watson-speech) in documentation. This is the code I'm using:
execute () {
var stream;
fetch('/api/speech-to-text/token').then(function(response) {
return response.text();
}).then(function (token) {
stream = WatsonSpeech.SpeechToText.recognizeFile({
token: token,
file: document.querySelector('#audiofile').files[0],
play: true, // play the audio out loud
outputElement: '#output', // CSS selector or DOM Element (optional)
}
});
stream.on('error', function(err) {
console.log(err);
});
}).catch(function(error) {
console.log(error);
});
}
There's a button that when clicked (after a file upload) runs this execute() function.
When I run this at the link where I found it (https://watson-speech.mybluemix.net/file-upload.html), it works just fine. I've configured the environment variables to use my API username and password (and confirmed they work by downloading an example React project from another Watson repository). When I try to do it in my own project however, it doesn't work. I get the following error:
Error during WebSocket handshake: Unexpected response code: 403
I'm not sure if this is a CORS issue, or if this can't be done all client-side or what. Does WebSocket need to be installed? I had thought it was all handled by the Watson functions, so I'm at a loss. Any help would be greatly appreciated!
Found out that you CANNOT retrieve an authorization token client side. You have to have some sort of server side code to retrieve the token, and then the client can use that. Watson also has a Node.js SDK that can be used for this with easy handling of authentication.

Authenticating using OfficeDev/office-js-helpers rather than adal

I'm working on an Office Add-in that currently uses adal to obtain an auth token.
As I want to use the Fabric front end I am changing it to React and I notice that the officer-js-helpers have implemented authenticators that seem to do the same job as the adal library.
Am I correct in this assumption? If so, how do I duplicate this adal config using the office-js-helpers authentication functions:
var adalConfig = {
instance: 'https://login.microsoftonline.com/',
tenant: 'myprivatesite.onmicrosoft.com',
clientId: 'xxx-xxx-xxx-xxx-xxx',
endpoints: {
'https://my.private.url.endpoint/path': 'https://myprivatesite.onmicrosoft.com/path.to.something',
}
And this token request:
var authContext = new AuthenticationContext(adalConfig);
authContext.acquireToken('https://myprivatesite.onmicrosoft.com/path.to.something', function (error, token) {
console.log(token)
});
UPDATE:
I have got the adal.js library working in my react app. I have used some of the code from the init function in the adalAuthenticationService angular provider to retrieve the authentication token.
So the question remains. Can I use the office-js-helpers to do the same thing?
Adal.js cannot be used out of the box for web add-ins authentication because within the sandboxed iFrame context of a web add-ins you cannot navigate simply to the authentication login page hosted outside your domain.
Office-js-helpers uses the dialogAPI when available and a popup as a fallback solution when not available.
If I remember correctly Office-js-helpers targets only Azure AD v2.0 (which comes with a lot of nice new features comparing to Azure AD). I guess it is a good choice.
I created an Open source sample the documentation can be interesting to you. However, this is not exactly what you want it is based on an AuthorizationCode flow while you are looking for Implicit flow.
OK It appears it is extremely easy. All that is required from the adal configuration is the client Id and the tenant.
if (OfficeHelpers.Authenticator.isAuthDialog()) {
return;
}
var authenticator = new OfficeHelpers.Authenticator();
authenticator.endpoints.registerAzureADAuth('xxx-xxx-xxx-xxx-xxx', //clientId
'myprivatesite.onmicrosoft.com' // tenant
);
authenticator.authenticate(OfficeHelpers.DefaultEndpoints.AzureAD)
.then(function (token) {
console.log(token);
.catch(function(error) {
console.log(error);
});

Categories

Resources