How to make a call from standard python GAE app to google function with authentication? - google-app-engine

I have a google function with removed allUsers permission.
I have a standard GAE application written in Python.
How to get proper auth token to make http call to the protected google function?
auth_token, _ = app_identity.get_access_token('https://www.googleapis.com/auth/cloud-platform')
headers = {'Authorization': 'Bearer {}'.format(auth_token)}
result = urlfetch.fetch(
url='https://us-central1-app_id.cloudfunctions.net/function-name',
method=urlfetch.POST,
headers=headers)
It looks like there are some answers here https://cloud.google.com/functions/docs/securing/authenticating but I could not figure out how to make it working.
Thank you in advance

I have followed the documentation you sent and tried to reproduce. I have used this sample for function to function and it worked fine for me. These are the lines within the sample code where the token is obtained:
metadata_server_token_url = 'http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience='
token_request_url = metadata_server_token_url + receiving_function_url
token_request_headers = {'Metadata-Flavor': 'Google'}
# Fetch the token
token_response = requests.get(token_request_url, headers=token_request_headers)
jwt = token_response.content.decode("utf-8")

Related

OKTA Invalid Scope issue s

I am trying to use OKTa for APP to APP authentication inside a SpringBoot Application and I get the below Scope issues ,
org.springframework.web.client.HttpClientErrorException$BadRequest: 400 Bad
Request: [{"error":"invalid_scope","error_description":"One or more scopes
are not configured for the authorization server resource."}]
we have default custome_scope mentioned at the Okta sever and when I try using the below code I am getting error while accesing line no 5 : in the below code snippet ,
String tokenUri = issuerUri + "/v2/token" + "? grant_type=client_credentials&response_type=token&scope=read";
RestTemplate rt = new RestTemplate();
headers = setHeaders(clientId, clientSecret, headers);
HttpEntity requestEntity = new HttpEntity(headers);
TokenData response = rt.postForObject(tokenUri, requestEntity, TokenData.class);
Can anyone help me

How Do I Authenticate a Service Account via Apps Script in order to Call a GCP Cloud Function

I'm trying hell hard to run a GCP Cloud Function from Apps Script. The Cloud function requires Authentication. However I keep getting thrown a "401 Error"
On My Google Project;
I've Created a Cloud function that requires Authentication
I've Created a Service Account that has Invoke (and edit) access to that function
I've Downloaded the JSON key for that service account and saved it as an object named CREDS in my Apps Script
This is my script so far:
const CREDS = {....JSON Key I downloaded from Cloud Console}
function base64Encode(str){
let encoded = Utilities.base64EncodeWebSafe(str)
return encoded.replace(/=+$/,'')
}
function encodeJWT(){
const privateKey = `Copied the PK from the CREDs file and replaced all the escaped whitespace as a string literal`;
let header = JSON.stringify({
alg: "RS256",
typ: "JWT",
});
let encodedHeader = base64Encode(header);
const now = Math.floor(Date.now() / 1000);
let payload = JSON.stringify({
"iss": "https://accounts.google.com",
"azp": "OAUTH CLIENT ID I CREATED ON GCP",
"aud": "OAUTH CLIENT ID I CREATED ON GCP",
"sub": CREDS.client_id,
"email": CREDS.client_email,
"email_verified": true,
// "at_hash": "TMTv8_OtKA539BBRxLoTBw", //Saw this in a reverse engineered Token but didnt know what to put
"iat": now.toString(),
"exp": (now + 3600).toString()
})
let encodedPayload = base64Encode(payload);
let toSign = [encodedHeader, encodedPayload].join('.')
let signature = Utilities.computeRsaSha256Signature(toSign, privateKey)
let encodedSignature = base64Encode(signature);
let jwt = [toSign, encodedSignature].join('.')
return jwt;
}
function testFireStore() {
let funcUrl = "https://[MY PROJECT].cloudfunctions.net/MyFunc"
const token = encodeJWT()
let options = {
headers:{
"Authorization": "Bearer " + token
}
}
let func = UrlFetchApp.fetch(funcUrl,options)
Logger.log(func.getContentText())
}
The actual Cloud func just gives a "Hello World" for now and it tests fine in the console
FYI, some steps I've done already
I've generated a token using gcloud on my local machine and used it in my apps script, that works fine
I've taken the said token and reverse engineered it on https://jwt.io
I've used the code here to create my JWT function which I checked back with https://jwt.io to ensure its in the correct format.
This Solution posted by #TheMaster in the comments to my solution solved the issue.
On the GCP side, I went in and enabled compute Engine and App Engine, then used this solution and it worked.
The only odd thing is that the target_audience requested there, I had to do a bit of reverse engineering to get it. I had to get the Identity Token from the command line tool, then use jwt.io to decode it, getting the AUD key...
but that aside, everythign worked like a charm

CompactToken validation failed 80049228

Some users are getting this error back when trying to sign in using Microsoft Sign In in order to access mail via MS Graph. I've had both corporate users and personal (Hotmail.com) users both showing this error number but it works fine for most users.
This is the call:
https://login.microsoftonline.com/common/oauth2/v2.0/token
This is the error returned:
Code: InvalidAuthenticationToken
Message: CompactToken validation failed with reason code: 80049228
Any pointers? Where can I find a reference to this error number?
This means the token expired and it need to be refreshed. If you want to refresh it without user interaction you'll need a refresh_token which is returned when you obtain a token initially.
Here is how you can refresh it:
function refreshTokenIfNeeded(tokenObj){
let accessToken = oauth2.accessToken.create(tokenObj);
const EXPIRATION_WINDOW_IN_SECONDS = 300;
const { token } = accessToken;
const expirationTimeInSeconds = token.expires_at.getTime() / 1000;
const expirationWindowStart = expirationTimeInSeconds - EXPIRATION_WINDOW_IN_SECONDS;
const nowInSeconds = (new Date()).getTime() / 1000;
const shouldRefresh = nowInSeconds >= expirationWindowStart;
let promise = Promise.resolve(accessToken)
if (shouldRefresh) {
console.log("outlook365: token expired, refreshing...")
promise = accessToken.refresh()
}
return promise
}
Where tokenObj is the token object you store in your database.
Make sure it also has expires_at or otherwise oauth2.accessToken.create() will create it and calculate from the current moment in time.
More details can be found in this tutorial and in this github repo (this is where the code above was taken from)
Found a Solution To This
In my case, I was refreshing the token before using the access_token with Microsoft Graph API even once.
Once you successfully call https://login.microsoftonline.com/common/oauth2/v2.0/token You will get a refresh_token and an access_token, my guess is that you have been refreshing the token before using the first access token from the URL mentioned above.
Steps to Fix:
Call https://login.microsoftonline.com/common/oauth2/v2.0/token as you did before
Copy the access_token from the response and use it at least once with your Microsoft Graph API
Now you can copy the refresh_token (or once the access_token is expired) and exchange for a new access token
Enjoy your API integration
Smile :)
Reference:
Microsoft Authentication (Tokens) Docs - Including Refresh Token
OneDrive Refresh Token Answer

how can I get refresh token

i learn this code sample :https://github.com/Azure-Samples/active-directory-dotnet-graphapi-web ,and yes ,i can get access token in AuthorizationCodeReceived :
AuthenticationHelper.token = result.AccessToken;
but how do i get the refresh token ?result.RefreshToken is not available , then how do i use acquiretokenbyrefreshtoken function ?
https://msdn.microsoft.com/en-us/library/microsoft.identitymodel.clients.activedirectory.authenticationcontext.acquiretokenbyrefreshtoken.aspx
The acquiretokenbyrefreshtoken function is available in ADAL 2.X , that code sample is using ADAL 3.13.8 , and from ADAL3.X, library won't expose refresh token and AuthenticationContext.AcquireTokenByRefreshToken function.
ADAL caches refresh token and will automatically use it whenever you call AcquireToken and the requested token need renewing(even you want to get new access token for different resource).
please see the explanation from here . Also click here and here for more details about refresh token in ADAL .
If you looking for a persistent mechanism, you can simply use TokenCache.Serialize()
Here's how I did it:
First, get the token and serialize the cache token
AuthenticationContext authContext = new AuthenticationContext($"https://login.microsoftonline.com/{Tenant}");
var authResult = authContext.AcquireTokenAsync(resource, ClientId, new Uri("https://login.microsoftonline.com/common/oauth2/nativeclient"), new PlatformParameters(PromptBehavior.SelectAccount)).Result;
byte[] blobAuth = authContext.TokenCache.Serialize();
Then, load the cached bytes
AuthenticationContext authContext = new AuthenticationContext($"https://login.microsoftonline.com/{tenant}/");
authContext.TokenCache.Deserialize(blobAuth);
var res = authContext.AcquireTokenSilentAsync(resource, clientId).Result;

Accessing authenticated Google Cloud Endpoints API from Google Apps Script

I'm trying to pull some data into a Google sheets spreadsheet from an API that's been built using Google Cloud Endpoints. Here is the API declaration:
#Api(
name = "myendpoint",
namespace =
#ApiNamespace
(
ownerDomain = "mydomain.com",
ownerName = "mydomain.com",
packagePath = "myapp.model"
),
scopes = {SCOPES},
clientIds = {ANDROID_CLIENT_ID, WEB_CLIENT_ID, API_EXPLORER_CLIENT_ID},
audiences = {WEB CLIENT_ID}
)
The method I'm trying to access has authentication enabled by means of the user parameter in the API declaration:
#ApiMethod(name = "ping", httpMethod = HttpMethod.GET, path = "ping")
public StringResponse getPing(User user) throws OAuthRequestException {
CheckPermissions(user);//throws an exception if the user is null or doesn't have the correct permissions
return new StringResponse("pong");
}
This works fine when using the generated client libraries or the gapi js library. However AFAIK I can't use those js libraries in Apps Script.
I've got an OAuth2 flow working using the apps-script-oauth2 library from here, and I'm pretty much using the default setup for creating the service
function getService() {
// Create a new service with the given name. The name will be used when
// persisting the authorized token, so ensure it is unique within the
// scope of the property store.
return OAuth2.createService(SERVICE_NAME)
// Set the endpoint URLs, which are the same for all Google services.
.setAuthorizationBaseUrl('https://accounts.google.com/o/oauth2/auth')
.setTokenUrl('https://accounts.google.com/o/oauth2/token')
// Set the client ID and secret, from the Google Developers Console.
.setClientId(CLIENT_ID)
.setClientSecret(CLIENT_SECRET)
// Set the name of the callback function in the script referenced
// above that should be invoked to complete the OAuth flow.
.setCallbackFunction('ruggedAuthCallback')
// Set the property store where authorized tokens should be persisted.
.setPropertyStore(PropertiesService.getUserProperties())
// Set the scopes to request (space-separated for Google services).
.setScope(SCOPES)
// Below are Google-specific OAuth2 parameters.
// Sets the login hint, which will prevent the account chooser screen
// from being shown to users logged in with multiple accounts.
.setParam('login_hint', Session.getActiveUser().getEmail())
// Requests offline access.
.setParam('access_type', 'offline')
// Forces the approval prompt every time. This is useful for testing,
// but not desirable in a production application.
.setParam('approval_prompt', 'auto')
//.setParam('include_granted_scopes', 'true');
}
These are my methods for accessing the APIs
function getDriveDocs() {
return executeApiMethod('https://www.googleapis.com/drive/v2/','files?maxResults=10');
}
function pingServer(){
return executeApiMethod('https://myapp.appspot.com/_ah/api/myendpoint/v1/','ping');
}
function executeApiMethod(apiUrl, method)
{
//var url = ;
var url = apiUrl + method;
var service = getRuggedService();
return UrlFetchApp.fetch(url, {
'muteHttpExceptions': true,
'method': 'get',
'headers': {
Authorization: 'Bearer ' + service.getAccessToken()
}
});
}
The getDriveDocs() method works perfectly, so I know my auth flow is working correctly. Also, if I call an unauthenticated method in my API I get the correct response. However, when I call the authenticated 'ping' method, the 'user' parameter is always null. Am I missing something in the fetch call? Everything I've read so far seems to suggest that setting
Authorization: 'Bearer ' + service.getAccessToken()
should be enough.
Any help would be much appreciated!
This turned out to be a simple mistake - I had created a new oauth2 credential in the google dev console and had not added the new client id to the API declaration. Here is the working API declaration:
#Api(
name = "myendpoint",
namespace =
#ApiNamespace
(
ownerDomain = "mydomain.com",
ownerName = "mydomain.com",
packagePath = "myapp.model"
),
scopes = {SCOPES},
clientIds = {ANDROID_CLIENT_ID, WEB_CLIENT_ID, API_EXPLORER_CLIENT_ID, GAPPS_CLIENT_ID},
audiences = {WEB CLIENT_ID}
)

Resources