Hello in this awesome 2022, that WILL be better!
here's the case we got and the problem we are facing:
We have two parts of the same project - firebase (firebase functions created in javascript - project A) as well as google cloud (cloud function created in python - project B).
From the FirebaseFunction part (A) we are sending a POST into the CloudFunction (B)
We want this request to be authenticated with the IAM on GCP side (B)
And so in project A:
const firebase = require("firebase-admin");
const botInformURL = "URLTOHIT";
const token = await firebase.auth().createCustomToken("XXX-notification-system");
const output = await axios.post(botInformURL, {
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
method: "post",
body: JSON.stringify({ additionalMessage }),
From the google cloud Platform (for CloudFunctions - in project B setup) I have added service account email ((...)projectA(...)iam.gserviceaccount.com) as a Cloud Functions Invoker (tried admin as well - without success)
And now - I am being blocked on the GCP with a 403.
What I am missing here?
When an unauthenticated caller sends a request to the Cloud Function, they will see a 401/403 status code response. In this scenario, the way out is to ensure that ‘allUsers’ has ‘roles/cloudfunctions.invoker’ role in the Cloud Function's IAM. You may refer to this documentation for more on this.
But yes, you are correct, if you use ‘allUsers’ it would expose the endpoint publicly and anyone would be able to access it.
If you want to avoid this, you have to follow the below steps:
From the receiving function:
1. You need to configure the receiving function to accept requests from
the calling function.
2. Use the gcloud functions add-iam-policy-binding command.
From the calling function:
1. Create a Google-signed OAuth ID token with the audience (aud) set to
the URL of the receiving function.
2. Include the ID token in an
Authorization: Bearer ID_TOKEN header in the request to the
function.
You may also refer to the Stackoverflow link for more information.
Related
When deploying Firebase Functions using the Firebase CLI, they are configured so that the Cloud Functions Invoker permission is granted to allUsers. With such a setting the code below functions as expected.
The Cloud Functions Invoker permission can also be granted to allAuthenticatedUsers. However, when I implement this change for addMessage, I only ever get a UNAUTHENTICATED error response using the code below.
Why won't allAuthenticatedUsers work for this Firebase Cloud Function?
Note: This Q&A is a result of a now-deleted question posted by Furkan Yurdakul, regarding why allAuthenticatedUsers wasn't working with his Firebase Callable Function for his Firebase app
MWE based on the documentation, with addMessage defined here:
firebase.auth().signInAnonymously() // for the sake of the MWE, this will normally be Facebook, Google, etc
.then((credential) => {
// logged in successfully, call my function
const addMessage = firebase.functions().httpsCallable('addMessage');
return addMessage({ text: messageText });
})
.then((result) => {
// Read result of the Cloud Function.
const sanitizedMessage = result.data.text;
alert('The sanitized message is: ' + sanitizedMessage);
})
.catch((error) => {
// something went wrong, keeping it simple for the MWE
const errorCode = error.code;
const errorMessage = error.message;
if (errorCode === 'auth/operation-not-allowed') {
alert('You must enable Anonymous auth in the Firebase Console.');
} else {
console.error(error);
}
});
Simply put, if the ID token passed to a Cloud Function represents a Google account (that used Google Sign-In through Firebase or Google itself), it works, otherwise, it doesn't.
Think of allAuthenticatedUsers as allAuthenticatedGoogleUsers instead of allAuthenticatedFirebaseUsers.
Background Information
For Callable Firebase Functions used with the Firebase Client SDKs, you will normally grant allUsers the permission to call it (the default setting Firebase CLI deployed functions).
A valid authenticated client request for a Google Cloud Functions must have an Authorization: Bearer ID_TOKEN header (preferred) or ?access_token=ID_TOKEN. Here, ID_TOKEN is a signed-in Google user's ID token as a JWT.
When Firebase Client SDKs call a Callable Function, they set the Authorization header for you with the current user's ID token (if the user is signed in, here). This is done so that the user's authentication token can be used in the context parameter of onCall() functions. Importantly though, a Firebase user's ID token doesn't always represent a Google user which makes it incompatible with allAuthenticatedUsers.
Because of this, you will have to gate your callable function in your code by checking context.auth and it's properties like below.
export const addMessage = functions.https.onCall((data, context) => {
if (!context.auth) {
// Throwing a HttpsError so that the client gets the error details.
throw new functions.https.HttpsError(
'failed-precondition',
'The function must be called while authenticated.'
);
}
// a valid user is logged in
// do work
});
Addendum on 403 Forbidden Errors
If your function is consistently throwing a 403 error after being deployed, this is likely because you are using an outdated copy of the Firebase CLI, as highlighted in the documentation:
Caution: New HTTP and HTTP callable functions deployed with any Firebase CLI lower than version 7.7.0 are private by default and throw HTTP 403 errors when invoked. Either explicitly make these functions public or update your Firebase CLI before you deploy any new functions.
I have an AWS Lambda function written in python that is initiated by a Zapier trigger that I set up. As I pass some input parameters to the function in the Zapier trigger, I can access to the input parameters in my python code by using variables such as event[parameter1]. It perfectly works.
I'm trying to access the same Lambda function in Airtable Scripting environment. In order to do it, I set up an API Gateway trigger for the Lambda function, but I can't figure out how to pass input parameters in the vanilla JS environment. Below is the code that I have, which gives me "Internal Server Error".
Your help would be definitely appreciated!
const awsUrl = "https://random-id.execute-api.us-west-2.amazonaws.com/default/lambda-function";
let event = {
"queryStringParameters": {
"gdrive_folder_id": consFolderId,
"invitee_email": email
}
};
let response = await fetch(awsUrl, {
method: "POST",
body: JSON.stringify(event),
headers: {
"Content-Type": "application/json",
}
});
console.log(await response.json());
[Edited] Plus, here's the code of the Lambda function and the latest cloudwatch log after a successful execution invoked by Zapier. It's a simple code that automates Google Drive folder sharing based on 2 inputs. (Folder ID + email address) Please bear with me for the poor code quality!
from __future__ import print_function
from googleapiclient.discovery import build
from google.oauth2 import service_account
SCOPES = ['https://www.googleapis.com/auth/drive']
SERVICE_ACCOUNT_FILE = 'service.json'
def lambda_handler(event, context):
"""Shows basic usage of the Drive v3 API.
Prints the names and ids of the first 10 files the user has access to.
"""
# 2-legged OAuth from Google service account
creds = service_account.Credentials.from_service_account_file(
SERVICE_ACCOUNT_FILE, scopes=SCOPES)
drive_service = build('drive', 'v3', credentials=creds)
# change multiple permissions with batch requests
folder_id = event['gdrive_folder_id']
email_address = event['invitee_email']
def callback(request_id, response, exception):
if exception:
# Handle error
print(exception)
else:
print("Permission Id: {}".format(response.get('id')))
batch = drive_service.new_batch_http_request(callback=callback)
user_permission = {
'type': 'user',
'role': 'writer',
'emailAddress': email_address
}
batch.add(drive_service.permissions().create(
fileId=folder_id,
body=user_permission,
fields='id',
))
batch.execute()
I'm not a Python expert and I don't know how you've setup your API Gateway integration with Lambda but I believe your code can have two issues:
1.) Internal Server Error as a response from the API Gateway endpoint also often refers to a problem in the integration between the API Gateway and your Lambda function. In this case here I can not see where you are returning a valid response back to the API Gateway. In your example the return value of batch.execute() is probably returned, right? However, by default the API Gateway expects an object that contains a statusCode and body and optionally headers. You can have a look at the AWS Lambda handler documentation for Python and their examples. Also this documentation page might be of interest for you.
2.) In your function you are accessing the event data like event['gdrive_folder_id']. However, I can not see that you are parsing the event data somewhere. Are you using a custom integration between your API Gateway? Because in case of a proxy integration the API Gateway sends an object that has a body field and from there you'd need to read the HTTP body. See examples on this documentation page.
Here are some more thing you can check on your own:
Have you also checked what you get when you just print the event data? Also, is the batch.execute() waiting for the batch processing or does it return anything? If so, what does it return?
One note here: You haven't told us anything about the integration between your API Gateway and your Lambda function. Since you can do some mapping between the API Gateway and AWS Lambda, it could be possible that you are converting the request and response outside of the Lambda function and hence, my suggestions above are wrong. Let me know if this is true or not and we can further investigate it.
I am trying to fetch git azure devops api to get information about repositories and branches in js.
In order to achieve that, I made a little application with the following code :
$(document).ready(function() {
var personalToken = btoa(':'+'<personnalAccessToken>');
fetch('https://dev.azure.com/<company>/<project>/_apis/git/repositories?api-version=5.1', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
'Authorization': 'Basic '+ personalToken
}
}).then(function(response) {
return response.json();
}).then(function(repositories) {
console.log("There are "+repositories.count+" repositories");
}).catch(function(error) {
console.log('Fetch error: ' + error.message);
});
This code is working great but as you can see there is my personnalAccessToken writen directly inside the code... which is really bad...
When I am using git in command line, I don't have to specify any credential information because I use git credential manager for windows. Which means my personnalAccessToken is already stored, cached and automatically used everytime I use a git command, like clone, etc.
So, I would like my js code to use the same thing, I would like it to use my stored credentials automatically to fetch the api without being required to set my personnalAccessToken in code.
I have already searched for hours but can't find out if it is possible.
I have already searched for hours but can't find out if it is
possible.
Sorry but as I know it's impossible. The way you're calling the Rest API is similar to use Invoke-RestMethod to call rest api in Powershell.
In both these two scenarios, the process will try to fetch PAT for authentication in current session/context and it won't even try to search the cache in Git Credential Manager.
You should distinguish the difference between accessing Azure Devops service via Rest API and by Code:
Rest API:
POST https://dev.azure.com/{organization}/{project}/{team}/_apis/wit/wiql?api-version=5.1
Request Body:
{
"query": "Select [System.Id], [System.Title], [System.State] From WorkItems Where [System.WorkItemType] = 'Task' AND [State] <> 'Closed' AND [State] <> 'Removed' order by [Microsoft.VSTS.Common.Priority] asc, [System.CreatedDate] desc"
}
Corresponding Code in C#:
VssConnection connection = new VssConnection(new Uri(azureDevOpsOrganizationUrl), new VssClientCredentials());
//create http client and query for resutls
WorkItemTrackingHttpClient witClient = connection.GetClient<WorkItemTrackingHttpClient>();
Wiql query = new Wiql() { Query = "SELECT [Id], [Title], [State] FROM workitems WHERE [Work Item Type] = 'Bug' AND [Assigned To] = #Me" };
WorkItemQueryResult queryResults = witClient.QueryByWiqlAsync(query).Result;
Maybe you can consider using a limited PAT, limit its scope to Code only:
I know there exists other Authentication mechanism
:
For Interactive JavaScript project: ADALJS and Microsoft-supported Client Libraries.
You can give it a try but I'm not sure if it works for you since you're not using real Code way to access the Azure Devops Service... Hope it makes some help :)
If you have the script set up in an Azure Runbook you can set it as an encrypted variable there and have it pull it from there before running rather than having it directly written into the code.
$encryptedPatVarName = "ADO_PAT"
$adoPat = Get-AutomationVariable -Name $encryptedPatVarName
$adoPatToken = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$($adoPat)"))
$adoHeader = #{authorization = "Basic $adoPatToken"}
The above is the Powershell version of it. I have seen some people do it with other
I would use Microsoft Graph API in my Angular Web application.
First I make connexion using msal library
When I try log in with my profil I get this error
I have configured my app as the mentionned in the official git sample
MsalModule.forRoot({
clientID: "Tenant ID",
authority: "https://login.microsoftonline.com/common/",
redirectUri: "http://localhost:4200/",
validateAuthority : true,
popUp: true
}),
Authetification is working and I get the token.
Then when I'm in home page I make a second request to Microsoft Graph API to get user information using that token.
getProfile() {
let header= new Headers();
let tokenid= sessionStorage.getItem('msal.idtoken');
header.set('Authorization', 'Bearer ' + tokenid)
let url ="https://graph.microsoft.com/v1.0/me/"
return this.http.get(url,{headers:header});
}
}
I get an 401 Unauthorized error with a response :
{
"error": {
"code": "InvalidAuthenticationToken",
"message": "Access token validation failure.",
"innerError": {
"request-id": "xxxxxx",
"date": "2018-10-09T22:58:41"
}
}
}
I don't know why MG API is not accepting my token, Am I using wrong authority url ?
UPDATE: I have understood that actually I get id_token which is different from access token. How can I get Access token from MSAL library to make MS GRAPH API calls ?:
According to the same sample you can also attach an HttpInterceptor that will automatically attach the access token to each (external) HTTP call.
By reading through the documentation I found the following information.
consentScopes: Allows the client to express the desired scopes that should be consented. Scopes can be from multiple resources/endpoints. Passing scope here will only consent it and no access token will be acquired till the time client actually calls the API. This is optional if you are using MSAL for only login (Authentication).
That suggests that using the HttpInterceptor doesn't only attach the access token, but also retrieves it. The token that you're seeing is probably just a token for your application, but isn't a valid token for the Graph API.
Internally it uses getCachedTokenInternal(scopes: Array<string>, user: User) to get a new access token for specific scopes code found here. I'm not sure if you can use this method as well to get a new token for that resource. I would just use the interceptor.
You could try to copy the access token and see how it looks like on jwt.ms (a Microsoft provided JWT token viewer) or jwt.io.
Any tokens valid for Graph should have the Audience of https://graph.microsoft.com, so if you inspect the token (in jwt.ms) it should at least have this value.
"aud": "https://graph.microsoft.com",
The issue is that you're using the id_token instead of the access token:
let tokenid= sessionStorage.getItem('msal.idtoken');
becomes something like:
let tokenid= sessionStorage.getItem('msal.token'); // or msal.accesstoken
Update(per Phillipe's comment)
You need to select the scopes that you want to target in your application. So, it looks like you want the user profile, so you'll want to add the consentScopes property to specify which scopes your app will use:
MsalModule.forRoot({
clientID: "Tenant ID",
authority: "https://login.microsoftonline.com/common/",
redirectUri: "http://localhost:4200/",
validateAuthority : true,
popUp: true,
consentScopes: ["user.read"]
}),
Make sure you add your endpoint to Resource Map configuration. See this link: https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/samples/MSALAngularDemoApp
export const protectedResourceMap:[string, string[]][]=[ ['https://graph.microsoft.com/v1.0/me', ['user.read']] ];
I'm trying to load an album from Google Photos via javascript but I don't understand how the api works, I started reading Google Photos API but no luck. Is there a code reference that I can follow to get a list of the photos of my album?
I found this but doesn't work
<script>
var scopeApi = ['https://www.googleapis.com/auth/photoslibrary', 'https://www.googleapis.com/auth/photoslibrary.readonly', 'https://www.googleapis.com/auth/photoslibrary.readonly.appcreateddata'];
function onAuthPhotoApiLoad() {
window.gapi.auth.authorize(
{
'apiKey': 'MY_API_KEY',
'client_id': "MY_CLIEND_ID",
'scope': scopeApi,
'immediate': false
},
handlePhotoApiAuthResult);
}
function handlePhotoApiAuthResult(authResult) {
if (authResult && !authResult.error) {
oauthToken = authResult.access_token;
GetAllPhotoGoogleApi();
}
}
function GetAllPhotoGoogleApi() {
gapi.client.request({
'path': 'https://photoslibrary.googleapis.com/v1/albums',
'method': 'POST'
}).then(function (response) {
console.log(response);
}, function (reason) {
console.log(reason);
});
}
onAuthPhotoApiLoad();
While in the process of developing a Photos synching script, I spent a few days researching and testing the Oauth 2.0 documentation. It's a lot to take in, but hopefully this Cliff-notes version is helpful:
App Setup You first need to get an application configuration through the developer console at console.developers.google.com/ and make sure that the Photos data is shared.
You'll get a JSON file that looks like this
{"installed":{
"client_id":"xxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com",
"project_id":"xxxx-xxxxxxxx-123456",
"auth_uri":"https://accounts.google.com/o/oauth2/auth",
"token_uri":"https://accounts.google.com/o/oauth2/token",
"auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs",
"client_secret":"xxxxxxxxxxxxxxxxxxxxxxxx",
"redirect_uris":["urn:ietf:wg:oauth:2.0:oob","http://localhost"]
}}
Request Authorization Code - You then need to write code that uses those values to get an authorization token - basically a string that indicates the user has allowed your application access to their data.
Send a request to the auth_uri endpoint with these values in the querystring:
scope - a space-delimited list of scopes from developers.google.com/photos that says you want your user to grant access to these features
redirect_uri - a URL you own that can capture an incoming querystring
client_id - from your developer config in step 1
state - 32 random bytes, base64 encoded and made URL-friendly by replacing "+","/","=" with "-","_","" respectively
code_challenge - a SHA256 hash of another 32 random bytes, base64 encoded and made URL-friendly
code_challenge_method - "S256" (no quotes)
Authorization round trip Sending this composed URI to a user's browser will allow them to choose a Google account and show which scopes are being requested. Once that form is submitted, it will redirect to your redirect_uri with querystring (Method = GET) values:
code - the authorization code you can use to request an access token
state - a string you can use to validate against your hash
Get an access_token Finally you exchange the authorization code for an OAuth AccessToken that you'll put in the HTTP header of all the API requests. The request goes to the token_uri from step 1 and has these request body (Method = POST) parameters:
code - you got from the redirect querystring in Step 3
redirect_uri - same as above, but this may not be used
client_id - from configuration
code_verifier - code_challenge before it was hashed
client_secret - from configuration
scope - can be empty here
grant_type - "authorization_code" (no quotes)
Use the access tokens The response from that request will have an access_token and a refresh_token. You can use the short-lived access_token immediately in your API request's HTTP header. Store the long-lived refresh_token so you can get a new access_token without authorizing again.
That's the gist of it. You can look at my Powershell script for an example of the authorization and authentication flows which work even though the rest is a little buggy and incomplete. Paging through albums is getting a 401 error sometimes.