Trouble authorizing access to App Engine via IAP - google-app-engine

I currently have App Engine up and running, protected by IAP, and my eventual aim is to have this be triggered by an Apps Script project. I've tested the code without IAP and it works fine. However, I'm running into difficulties successfully authorizing access to it when IAP is enabled.
I've added myself as an IAP-secured Web App User (as well as Policy Admin) to the App Engine, but whenever I try triggering it from a GSheets Apps Script where I'm the owner and it's associated with the correct GCP project (using this great explanation as a guide) I get the following:
"Invalid IAP credentials: JWT audience doesn't match this application ('aud' claim (1084708005908-bk66leo0dnkrjsh276f0rgeoq8ns87qu.apps.googleusercontent.com) doesn't match expected value (1084708005908-oqkn6pcj03c2pmdufkh0l7mh37f79po2.apps.googleusercontent.com))"
I've tried adding/removing various permissions to my account, as well creating a new Apps Script and re-adding to the project, but to no avail. I run into the same issue when triggering from CLI, so I'm fairly sure it's an issue with authentication, however this is my Apps Script code in case it helps:
function test() {
const options = {
headers: {'Authorization': 'Bearer ' + ScriptApp.getIdentityToken()},
muteHttpExceptions: true
}
var result = UrlFetchApp.fetch('https://APP-ENGINE-URL.appspot.com', options);
Logger.log(result);
}
And the manifest file:
{
"timeZone": "Europe/London",
"dependencies": {
},
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8",
"oauthScopes": ["openid", "https://www.googleapis.com/auth/script.external_request"]
}
Any help on this is super appreciated! Never posted here before, but pretty desperate and couldn't find anyone with this exact problem on SO.

Consideration
The problem with your solution is that you are using the identity of an auto-generated OAuth Client for Apps Script. This clients are not suitable for this kind of authentication, here a complete list of supported OAuth clients.
Solution
In order to complete your authentication you will need an extra step. You will have to create another OAuth Client and build an identity token with its credentials.
To make things easier I would recommend to use this Apps Script library: https://github.com/gsuitedevs/apps-script-oauth2
The inital Set-up is covered in the linked documentation.
Important: When creating the OAuth Client take note of the ClientID and the Client-secret. Plus, you will need to add an Authorized Redirect URI. This is standard when using the OAuth2 GAS library and it has this form: https://script.google.com/macros/d/{Your Apps Script ID}/usercallback
Now you have all the necessary information to build your identity token. In the Github repository there is a boilerplate sample that will cover the first coding steps with the OAuth2 GAS library.
Here is the link.
Copy this code to your Apps Script project and follow the instructions in the comments. You will need to add an extra OAuth scope: "https://www.googleapis.com/auth/userinfo.email".
Once you set all the constants with your OAuth clients information you should run the run() function from your Apps Script editor. This will log a URL you have to open in your browser to authorize your App. Once you authorized the App run again the run() function and you will successfully access your IAP protected application.
References
OAuth2 GAS library
IAP programmatic authentication

Related

How to set up access to Azure blob storage from React app (deployed in Azure web app) with credentials from browser?

I got stuck on trying to access Azure blob storage from React app (thus from browser) with credentials.
And firstly I want to admit, that I am newbie in Azure, so maybe I misunderstood some basic concepts...
My current situation and goal:
I am developing React app (lets say MyReactApp). This app uses some files from Azure blob storage (lets say MyBlobStorage) -> it reads, creates and deletes blobs.
I started to develop it on my local and for this dev purpose I was connecting to MyBlobStorage with SAS - this worked perfectly.
MyReactApp is browser only, so it does not have any backend.
After finishing local development, I deployed it as Azure web app with SAS. What have I done:
created App service (lets say MyAppService)
register app in Azure Active Directory and use it as Identity Provider in MyAppService
After this the app works from Azure url perfectly too.
But my app on Azure should fulfill 2 conditions:
User has to log in with AAD account before access to MyReactApp
App itself must get rid of SAS (because it is not secure as it can be obtained from browser) and use some Azure credentials to connect to Azure blob storage
First condition - user log in:
I enabled "easy" logging in MyAppService -> Authentication and chose users, who can have access.
in Authentication section of app in AAD I set up Web type Redirect Uri as /.auth/login/aad/callback
Still everything works great - the user, who is assigned to the app, can log in and work with the app - so far so good, but now the problem comes
Second condition - I wanted to get rid of the SAS to access MyBlobStorage and use DefaultAzureCredentials:
I turned on managed identity for MyAppService and add it as Storage Blob Data Contributor for MyBlobStorage
I obtained user_impersonation and User.Read Api permissions for my app
I removed SAS and tried to add DefaultAzureCredentials to my code -> but it seems, that they can't be used in browser and only option is InteractiveBrowserCredentails
so I tried to use InteractiveBrowserCredentails like this:
this.interactiveBrowserCredential = new InteractiveBrowserCredential({
tenantId: "<appTenant>", // got from app registration on AAD
clientId: "<appClient>", // got from app registration on AAD
redirectUri: <MyAppServiceURi>/.auth/login/aad/callback // the same as in Azure AAD app,
});
this.blobSC = new BlobServiceClient(Constants.STORAGE_PATH, this.interactiveBrowserCredential);
My current result:
This shows login popup after getting to the page and after another signing in it results in error:
AADSTS9002326: Cross-origin token redemption is permitted only for the
'Single-Page Application' client-type.
I googled it of course and according to some answers I tried to change the Web type of redirect URI to SPA.
I tried it, but some other error showed up:
AADSTS9002325: Proof Key for Code Exchange is required for
cross-origin authorization code redemption.
Surprisingly this should be solved by changing from SPA to Web type...:) So I am trapped...
My expected result
In ideal world, I want to connect MyReactApp to MyBlobStorage without popup and with some secret credentials, just to say something like this.blobSC = new BlobServiceClient(Constants.STORAGE_PATH, credentials);
and be able to work with blobs.
Is it possible to access blob storage from browser without errors of course and ideally without popup, which needs another log in for user?
My complementary questions
Can I use somehow the logging info from the user (from his "easy" AAD logging)? I can get his info with GET call to /.auth/me, maybe it can be utilized, so I can use his info to access the blobs?
The solution should be working on localhost too (I tried to add http://localhost:3000/ to redirect uri, but without success), can it be done?
Thank you all, who read the whole story!

Is there a way to use the authentication retrieved from Microsoft Teams (via TeamsFX) and use it for an internal web API?

I am currently working on a Tab application for Microsoft Teams and have stumbled upon the extension called TeamsFx which enables easy retrieval of an authentication token from Teams without the need of prompting the user every time. Likewise, I used the Teams Toolkit extension for Visual Studio to get started. The project is implemented with .NET 5.0.
What I need, is to use the same authentication token retrieved from Teams inside the tab to then access a web API that is implemented in the same project. I have spent two full days trying to work this issue out with pretty much no result. I can retrieve an authentication token via TeamsFx, but simply using this as the bearer auth token in my web api did nothing but it telling me that the signature was wrong.
I have tried adding something like this to my Startup class:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(Configuration, "AzureAd");
,but then it threw an exception because the TeamsFx library has already done the same with this call:
services.AddTeamsFxSimpleAuth(Configuration);
In previous Web API projects I have been able to set up Azure AD authentication to the site, and pretty seemlessly add the [Authorize] tag to any WebAPI classes and it just worked.
I hope that someone can save me from my own insanity ;D
Best,
//F_
I think this is the same question which created in Github, it is already answered and got the solution.
https://github.com/OfficeDev/TeamsFx/issues/1363
Since we only got JavaScript version of microsoft/teamsfx sdk, so we including webpack to wrapper it and using JSRuntime to trigger it in Blazor.
The webpack source code is in JS/src/index.js, you could add these code to get SSO token
export async function getSsoToken() {
var credential = new TeamsUserCredential();
var token = await credential.getToken("");
return token;
}
Then under JS folder , run 'npm install' and 'npm run build' to webpack the code and refresh the webpack file (wwwroot/teamsfx.js).
Finally you could using JSRuntime trigger the new func you just added in TeamsFx.cs.
await jsRuntime.InvokeAsync<AccessToken>("TeamsFx.getSsoToken");

Service to service requests on App Engine with IAP

I'm using Google App Engine to host a couple of services (a NextJS SSR service and a backend API built on Express). I've setup my dispatch.yaml file to route /api/* requests to my API service and all other requests get routed to the default (NextJS) service.
dispatch:
- url: '*/api/*'
service: api
The problem: I've also turned on Identity-Aware Proxy for App Engine. When I try to make a GET request from my NextJS service to my API (server-side, via getServerSideProps) it triggers the IAP sign-in page again instead of hitting my API. I've tried out a few ideas to resolve this:
Forwarding all cookies in the API request
Setting the X-Requested-With header as mentioned here
Giving IAP-secured Web App User permissions to my App Engine default service account
But nothing seems to work. I've confirmed that turning off IAP for App Engine allows everything to function as expected. Any requests to the API from the frontend also work as expected. Is there a solution I'm missing or a workaround for this?
You need to perform a service to service call. That's no so simple and you have not really example for that. Anyway I tested (in Go) and it worked.
Firstly, based your development on the Cloud Run Service to Service documentation page.
You will have this piece of code in NodeJS sorry, I'm not a NodeJS developer and far least a NexJS developer, you will have to adapt
// Make sure to `npm install --save request-promise` or add the dependency to your package.json
const request = require('request-promise');
const receivingServiceURL = ...
// Set up metadata server request
// See https://cloud.google.com/compute/docs/instances/verifying-instance-identity#request_signature
const metadataServerTokenURL = 'http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience=';
const tokenRequestOptions = {
uri: metadataServerTokenURL + receivingServiceURL,
headers: {
'Metadata-Flavor': 'Google'
}
};
// Fetch the token, then provide the token in the request to the receiving service
request(tokenRequestOptions)
.then((token) => {
return request(receivingServiceURL).auth(null, null, true, token)
})
.then((response) => {
res.status(200).send(response);
})
.catch((error) => {
res.status(400).send(error);
});
This example won't work because you need the correct audience. Here, the variable is receivingServiceURL. It's correct for Cloud Run (and Cloud Functions) but not for App Engine behind IAP. You need to use the Client ID of the OAuth2 credential named IAP-App-Engine-app
Ok, hard to understand what I'm talking about. So, go to the console, API & Services -> Creentials. From there, you have a OAuth2 Client ID section. copy the Client ID column of the line IAP-App-Engine-app, like that
Final point, be sure that your App Engine default service account has the authorization to access to IAP. And add it as IAP-secured Web App User. The service account has this format <PROJECT_ID>#appspot.gserviceaccount.com
Not really clear also. So, go to the IAP page (Security -> Identity Aware Proxy), click on the check box in front of App Engine and go the right side of the page, in the permission panel
In the same time, I can explain how to deactivate IAP on a specific service (as proposed by NoCommandLine). Just a remark: deactivate security when you have trouble with it is never a good idea!!
Technically, you can't deactive IAP on a service. But you can grant allUsers as IAP-secured Web App User on a specific service (instead of clicking on the checkbox of App Engine, click on the checkbox of a specific service). And like that, even with IAP you authorized all users to access to your service. it's an activation without checks in fact.

How to Authenticate WPF Application with AAD B2C to gain access to Azure App Service

I'm quite stuck at the moment trying to implement authentication into a project I'm working on. The end goal of this project is to have two WPF apps and on web based app hosted on Azure. One WPF app is for an administrator, the other for staff, and lastly the web app for customers. Each application will be connected to one Azure App Service for a shared database and needs to have authentication so separate all the users. For authentication I am planning on using Azure Active Directory B2C.
I've been researching and trying to implement this for several days now on one of the WPF apps but as I stated before I'm quite stuck. From what I understand, the only way to do B2C authentication for WPF is through client managed authentication. Following the code shown on the Azure tutorial sites, other SO posts, and the Azure Git Repos, I have come up with the following code:
System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12;
authResult = await App.PublicClientApp.AcquireTokenAsync(App.ApiScopes,
GetUserByPolicy(accounts, App.PolicySignUpSignIn), UIBehavior.SelectAccount,
string.Empty, null, App.Authority);
Newtonsoft.Json.Linq.JObject payload = new Newtonsoft.Json.Linq.JObject();
payload["access_token"] = authResult.AccessToken;
MobileServiceClient msclient = new MobileServiceClient(App.AzureAppService);
MobileServiceUser user = await msclient.LoginAsync(
MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory, payload);
Everything starts off great and I'm able to get my Sign-In policy to display. After logging in, I am given an IdToken and an AccessToken. After creating a JObject and adding the access token to it, I attempt to use it to login with my MobileServiceClient. But that's where I am having issues. No matter what I do, no matter what I try, I only get an exception with a 401 Error telling me I'm unauthorized. And this is the point I've been stuck at for the past few days.
Obviously I'm not doing anything special here and I imagine many people have done this before me but I just can't seem to get past this point and was hoping someone may be able to offer me some guidance. Am I way off track? Is there a better way that I could be doing this? Any suggestions or advice would be greatly appreciated as I am very new to Azure.
Thanks all!
Update:
Here's how I have my Azure Settings:
On the app service side
Client Id: "{Client Id of the AAD B2C App}"
Issuer URL: "login.microsoft.com{TennatName}.onmicrosoft.com/v2.0/.well-known/openid-configuration"
Allowed Token Audiences: "https://{App Service Name}.azurewebsites.net" (App Service URL)
On B2C side:
Web and native client enabled
Web Reply URL: "https://{AppServiceName}.azurewebsites.net/.auth/login/add/callback"
Native App: I did not know what custom redirect URL to have so I have both
"{TennatName}.onmicrosoft.com://auth/" and
"{AppServiceName}.azurewebsites.net/.auth/login/add/callback"
Update 2:
My authority is login.microsoftonline.com/tfp{tenant}/{policy}/oauth2/v2.0/authorize
And my ApiScopes = { "https://{Tenant}/thisisatest/user_impersonation" };
If the authority for the client is set to https://{your-tenant-name}.b2clogin.com/tfp/{your-tenant-name}.onmicrosoft.com/{your-policy-name}/, then the issuer URL in the app service must refer to the metadata for this authority; i.e. https://{your-tenant-name}.b2clogin.com/tfp/{your-tenant-name}.onmicrosoft.com/{your-policy-name}/v2.0/.well-known/openid-configuration.

Microsoft Graph appRoleAssignment direct queries not supported?

I've been using these github documentation pages: Page1, Page2, I've implemented adal on the app that I'm using and I'm just logging the token on the console so that I can just copy-paste the token into postman and try requests faster.
I've been trying a get request as mentioned on the documentation to.
https://graph.microsoft.com/beta/appRoleAssignments/{Object Id of AD App} or { App ID of AD App }
But also attempted various other routes based on the documention. I'm getting 'resource not found' for all of the other ones and 'direct queries to this resource type are not supported' for the one above.
Brosed a few related stackoverflow threads and went into github issues to see anything related to approlesassignment resource type but no luck.
EDIT: Token is obtained in Angular app using package adal-angular4 (it is however an angular6 application and the package has been updated to work with Angular 6)
After a lot of struggling, I managed to get the necessary info via Azure Enterprise App API, not th endpoints mentioned above.
This is how the GET request looks like:
https://graph.microsoft.com/beta/servicePrincipals/{ID}/appRoleAssignments/
ID in the context above is "Object ID" of the Azure AD App under 'ENTERPRISE APPS', taking the one from under 'enterprise apps' is critical.
You'll find it in:
In Azure > Active Directory > Enterprise Apps > Your App > Properties
Now you just need bearer authentication token in the header for Microsoft Graph and the right permissions, I think Read/write all directory data should be good enough.

Resources