Cloud Identity Platform make Custom SAML ACS Callback - google-cloud-functions

I'm trying to make a custom SAML app to integrate with Google Workspace (i.e. so that if a person in the organization wants to access it, they could do so from the apps list on google.com).
Because Google Cloud Identity Platform only supports service provider-initiated login, this does not seem possible using the default callback URL they provide. I saw this answer to a similar question, and was hoping to implement something like this. However, the SAMLResponse coming in seems to be encrypted, and I don't know enough about the encryption process to know how to decrypt it (or if that's even possible).
I'm using a Cloud Function as my callback URL, and to be clear I'm trying to decrypt the res.body.SAMLResponse string:
exports.samlACSCallback = functions.https.onRequest(async (req, res) => {
console.log(req.body.SAMLResponse)
})
My best guess is that it's somehow related to the certificate that I had to copy from the Google Admin console to the Cloud Identity setup page?

Related

Calling Firebase Hosting API from a Firebase Cloud Function

I have a Firebase (node.js) cloud function that pulls in some data from my app's Firestore database and builds some static content for the web. I'd like that same cloud function to deploy the static content to Firebase hosting via the Firebase Hosting API, creating a static portion of my site with user generated content.
I understand the general flow thanks to the somewhat clear walkthrough, but am stuck on the first step: getting an access token to call the API. Obviously I'm not going to insecurely put my service account key in the cloud function itself, so the example in the walkthrough doesn't apply. And as I understand it, Firebase cloud functions are already associated with a service account, so presumably there's some way to get an access token to call other Google Cloud services from a cloud function.
So how do I get an access token to call the hosting API from a Cloud Function?
There are some red flags that make me think this isn't possible. For example, all of the uses cases in the walkthrough allude to other server environments, as opposed to Google Cloud environments. And yet, this use case is the third bullet in the use case list in the walkthrough.
I've searched extensively here and elsewhere for some guidance, but aren't finding anything. There are some older questions about accessing hosted files from a cloud function that aren't relevant. This promising question from 5 years ago about this exact use case only has dead ends.
You can use the google-auth-library package in Cloud Functions to a get a token as shown below:
import { GoogleAuth } from "google-auth-library";
const token = await new GoogleAuth({
scopes: ["https://www.googleapis.com/auth/cloud-platform"],
}).getAccessToken();
If you use Firebase Admin SDK in the Cloud Functions, then you can get an access token of the default service account as shown below (do ensure the service account has required permissions):
import { initializeApp } from "firebase-admin/app";
const admin = initializeApp();
const token = await admin.options.credential?.getAccessToken();
// ^ Google OAuth2 access token object used to authenticate with Firebase services.

security doubts about google cloud functions

I've been reading a lot of questions here about security regarding cloud functions (HTTP triggered) and I also read google's official docs but I couldn't find a clear answer for some questions, so I need help.
Please note that this question is about google's cloud functions made from Google cloud console, nothing to do with firebase.
It's possible to make a function "callable" just from my website? I tried to use cors policy but I have it clear that cors have nothing to do with security, so I'm a little bit worried about how I can keep my cloud function "callable" just from my domain.
On the other hand I created a service account on Google Cloud Platform and I'm trying to use it as an invoker. I have set my service account as invoker but how do I use that on my server?
CASE: I'm creating a log for my web, so I created a cloud function that I call every time someone accesses my site: (I'm using Google Tag Manager server-side).
const sendHttpRequest = require("sendHttpRequest");
const postBody = {
testing : true
}
//Calls cloud function
sendHttpRequest(
"<CLOUD FUNTION TRIGGER ADDRESS>",
(statusCode, headers, body) => {
setResponseStatus(200);
setResponseBody("done");
},
{
headers: { "content-type": "application/json; charset=utf-8", "Origin" : "https://example.com" },
method: "POST",
},
postBody
);
}
I would like to know how I can be sure that this cloud function can only be invoked by my server.
Thanks in advance!
Yes, it's possible.
See Authenticating for Invocation.
The second paragraph provides a good synopsis of why this has some complexity.
You're correct in using a Service Account. Service Accounts are used by software. User accounts are used by humans.
It's unclear where your website is running but it will need to generate an identity token (aka JWT) in order to securely invoke the remote Cloud Function.
See the developer testing example in which an identity token is provided by the Cloud SDK (gcloud) using gcloud auth print-identity-token and then used as the Authorization header value with curl.
That's what your website needs to replicate.
The page recommends (correctly) considering using one of Google SDKs to generating tokens programmatically, because the alternative is gnarly and prone to error.
Unless your website is also running on GCP, you can't use the metadata service .

To authenticate the client that invokes Google cloud function in Java

I have a google cloud function in Java.
Client will invoke the function using HTTP trigger URL.
But that is not secure. I have gone through some docs saying that you should pass a token or client ID and then verify it in server side.
Can anyone explain that in detail and please provide a code example if any.
My doubt is to authenticate the client while they invoke the function using Http trigger
This page explains quite well all the capacity that you have to authenticate a requester on Cloud Functions.
If you have users, the best way is to use Firebase Auth (our Google Cloud Identity Platform which is simply a more advance solution than Firebase Auth with more features)
However, you need to grant all you user with cloudfunction.invoker role, to allow them to invoke the Cloud Functions. It could be difficult. You can also perform the check on your side, but in this case you remove the security (filter) layer of google and you have to check all the traffic by yourselves (not really safe, in term of billing and in case of attack).
The latest solution, API keys, is not recommended, especially for the users. But for machine to machine it's sometime the only solution. However, there isn't out of the box solution and for this I wrote an article, that explains how to create a Cloud Endpoint (or now a Cloud API Gateway which is the serverless solution of Cloud Endpoint with ESPv2) to accept API Keys.
With this latest solution, if you change your security definition, you can also accept OAuth2 tokens coming from Firebase Auth (or Cloud Identity Platform), but this time, you don't need to grant all the users on your Cloud Functions IAM role. The token only need to be valid and it's the Cloud Endpoint service account which is used to perform the call (and thus which needs to be authorized on the Cloud Functions).
In addition, because you can accept OAuth2 token, you can also accept non Google token, and thus have your users in any IDP OAuth2 compliant (KeyCloak, Okta,...)
You could use external OAuth server like keycloack (https://github.com/keycloak/keycloak), or use somethging like Json Web Tokens -- https://jwt.io/ -- available for various languages, siutable for microservices.

How to Sign Into Specific Google Drive Account through Javascript Google Drive API

I am trying to run a script off of my Google Drive through the Javascript Google Drive API. This works fine, but only if I sign into my account on the popup that opens. I wish to sign into the same account every time and so was wondering if there was any way to automate the login of this so as to bypass users having to enter in that login information.
In short, you would have login at least once, everytime after the Google Identity Provider JSON Web Token expires. I am not sure how long this would be with the Goolge Drive API, but typically these tokens may be valid for anywhere from a single request to days long.
Here is the Documentation for the Google API OAuth2
https://developers.google.com/identity/protocols/OAuth2
Refresh the access token, if necessary.
Access tokens have limited lifetimes. If your application needs access
to a Google API beyond the lifetime of a single access token, it can
obtain a refresh token. A refresh token allows your application to
obtain new access tokens.
Note: Save refresh tokens in secure long-term storage and continue to
use them as long as they remain valid. Limits apply to the number of
refresh tokens that are issued per client-user combination, and per
user across all clients, and these limits are different. If your
application requests enough refresh tokens to go over one of the
limits, older refresh tokens stop working.
Google has provided a quickstart guide for implementing a user sign via Google Apis. Google uses the OAuth2 protocol in which you must register with Google as a Client application. Once registered as a Client application, you will be issued a Client ID, which you typically provide to your application in some form of application initialization.
Here is a link to their quickstart guide, which will help you get started:
https://developers.google.com/drive/v3/web/quickstart/js
Note that this is a basic example that does not demonstrate how you may approach persisting a JSON Web Token so that the user does not have to login on every request. I outline a simple approach of managing Authentication in JavaScript and Angular to get you moving in the right direction, but incomplete, direction.
For example, in Angular:
// Configures the required variables before Running an Instance of the App
angular.module("yourModuleName").config(configureApp);
AND
// Executed when the App Instance launches, allowing you to connect to Google APIs when the App starts
angular.module("yourModuleName").run(runApp);
Where configureApp and runApp are JS functions that handle application initialization in the AngularJS Framework. The code in the follow example would retrieve the Apps Google Client ID from their own App's REST API. This is just an example of where you could retrieve these credentials from storage, but most likely is not the most secure example:
var configureApp = function($http,$window) {
// Setup your CLIENT ID from your own REST API (or any other mechanism you may choose)
var httpPromise = $http.get("http://myApp.myDomain.com/config/googleClient");
// Handle the Response from the above GET Request
httpPromise.then(
// Handle Success
function(response) {
// Store the CLIENT ID in local storage for example
$window.localStorage.setItem("GOOGLE_API_CLIENT_ID", response.data.clientId);
// Setup the App Wide variables for Google API
// Client ID and API key from the Developer Console
var CLIENT_ID = response.data.clientId;
// Array of API discovery doc URLs for APIs used by the quickstart
var DISCOVERY_DOCS = ["https://www.googleapis.com/discovery/v1/apis/drive/v3/rest"];
// Authorization scopes required by the API; multiple scopes can be
// included, separated by spaces.
var SCOPES = 'https://www.googleapis.com/auth/drive.metadata.readonly';
// Do more initialization configuration
};
var runApp = function() {
// Initialize the API
gapi.client.init({
discoveryDocs: DISCOVERY_DOCS,
clientId: CLIENT_ID,
scope: SCOPES
}).then(function () {
// Listen for sign-in state changes.
gapi.auth2.getAuthInstance().isSignedIn.listen(updateSigninStatus);
// Handle the initial sign-in state.
updateSigninStatus(gapi.auth2.getAuthInstance().isSignedIn.get());
authorizeButton.onclick = handleAuthClick;
signoutButton.onclick = handleSignoutClick;
});
}
Which function to use with Angular would depend on the desired app lifecycle you need to target in an Angularjs app. This approach can be applied in other JS frameworks like React and Backbone.
To highlight another perspective from the documentation, updateSigninStatus would be a great place to capture the JSON Web Token returned by Google's Authorization request at which point you could store this token in the browser's window.localStorage for re-use.
You then could reuse the token whenever the Google API requires authentication. Tokens typically have an expiration. Until the token expires, you would be able to prevent the API from displaying a login modal.
This does mean you would still have to manage the logic behind the Authorization process using this approach, monitoring any response from Google requesting a token refresh or re-authentication.
Auth0 is a great Authentication and Authorization plugin available in many languages for connecting with Google and many other OAuth2 Identity Providers. The Google Drive API uses their own Identity Provider Service to confirm the Identity of your apps users in tandem with your registered app's Client ID.
Here are links that I found when implementing Authorization for a project that required me to implement Authorization using the Google Identity Provider:
https://jwt.io/
https://auth0.com/
Best practices for authentication and authorization in Angular without breaking RESTful principles?
https://thinkster.io/tutorials/angularjs-jwt-auth
You are saying that all users login to the same Google account?
In that case you have 2 options.
1/ write a server application that has a stored refresh token. Create an endpoint that allows an authenticated user to request an access token.
2/ embed a refresh token in your JavaScript, but make sure that only authenticated users can load the JS

Google Apps Script - Server Side Authentication

I would like to use GAS script as some sort of a web service. Basically, I would like to post some parameters and then using Drive service do some manipulations of Google Doc and all of this has to be done from backend, without use of browser. Now I have issue with authentication - When I deploy GAS as a Web App (Execute the app as user accessing the app, Anyone can access app), when trying to execute script via Http request, I get Google Login response (/accounts/ServiceLogin). Obviously user has to authenticate access. I was not able to find if we can apply OAtuh at that point. So my question is - What can be done to achieve this? Is there any mechanism that can be used to atuhenticate to GAS from a server side? Or am I forced to use browser with GAS?
You could have your own authentication check in the Apps Script code. You would have NO security settings with the publishing, but implement your own password check in the Apps Script code. You would set the publish "Execute the app as:" setting to ME, and the setting of "Who has access to the app:" to Anyone, even anonymous. That allows the app to be run by anyone with no authentication. Then create your own authentication by passing a password in the URL search string.
Even though putting a password in the URL search string is encrypted over a HTTPS connection, it's still considered "bad practice", because the search string could be stored in plain text in the browser history. But you aren't going to use a browser.
So, your Apps Script App would read a search string parameter from the URL, and either allow the script to be run or not. You would be implementing your own authentication system.
But you need to evaluate and decide what the security is on your server, or whatever server is sending the request. If you can send a HTTPS request to an Apps Script with a password in the URL search string, and it's not a security concern on your server, or whatever server is sending the HTTPS request, then you can consider that and make your decision.
If you want to get a response back from Apps Script to your server without anything opening up in the browser, use the Content Service:
Google Documentation - Content Service