How to generate OAuth2.0 Bearer when calling App Engine Endpoints API? - google-app-engine

I am providing a REST API via App Engine. I used Cloud Endpoints to generate it, although the client will not be mobile Android/iPhone but rather a known web server. Since I am familiar with this server (it is part of my application), I decided to use Service Account authorization in order to authorize the API calls (In addition, I will do IP validation, but that's beside the point).
I made all the necessary arrangement, created a Google developer project, generated a service account id (and email), with a p12 file, and added all the annotations needed on the server side (including a User object in the implementing function).
Now I want to implement a call to this API, and in order for it to work, I need to include a proper authorization header in my request.
When working with Google APIs, the client libraries generate some Credential object which you later need to pass in building some Service object, which represents a Google API you wish to call. For example, if you want to access Drive API in Java, you will do:
Drive drive = new Drive.Builder(Globals.httpTransport, Globals.jsonFactory, credential).build();
Where credential object is the object I previously build as follows:
credential = new GoogleCredential.Builder().setTransport(Globals.httpTransport)
.setJsonFactory(Globals.jsonFactory)
.setServiceAccountId(serviceAccountEmail)
.setServiceAccountScopes(scopes)
.setServiceAccountUser(serviceAccountUser)
.setServiceAccountPrivateKeyFromP12File(file(serviceAccountPrivateKeyP12File))
.build();
However, in my case, the client is not calling a Google API, but rather my App Engine REST API. How do I go about generating (or using the credential object I created to obtain) a proper Authorization header?

You can find some documentation in the readme.html file that is generated alongside the bindings, and here.
You can get the following account information in the console, "Apis & Auth", "Credentials". Here you need to paste "Email Address" of the service account. Your #Api annotation should include the account's "Client Id" in the "clientIds" parameter.
String accountEmail = "your-service-account#developer.gserviceaccount.com";
String keyFilePath = "your-key-file.p12";
This is the minimum authorization scope that is required for Cloud Endpoints API. It only allows the app to access the user's e-mail address. Your #Api annotation should list it in the "scopes" parameter.
String emailScope = "https://www.googleapis.com/auth/userinfo.email";
Then you need to create some support objects and the credential. GsonFactory can be replaced with JsonFactory if you prefer.
HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
GsonFactory gsonFactory = new GsonFactory();
GoogleCredential credential = new GoogleCredential.Builder()
.setTransport(httpTransport)
.setJsonFactory(gsonFactory)
.setServiceAccountId(accountEmail)
.setServiceAccountScopes(Collections.singleton(emailScope))
.setServiceAccountPrivateKeyFromP12File(new File(keyFilePath))
.build();
And finally create your API client. Replace YourApi with the client from the generated bindings. If you want to test against the dev AppServer, you can call .setRootUrl(yourDevServerUrl + "/_ah/api") on the builder.
YourApi client = new YourApi.Builder(httpTransport, gsonFactory, credential)
.setApplicationName("YourClientName")
.build();

Related

How to get session id in static method while making callout

I am working on something which includes LWC with tooling API. I wrote this below method which makes a callout. but when I call this method this method from lwc at that time I'm unable to get session Id, but if I call this same method from the developer console then it works fine.
Apex Code:
#AuraEnabled
public static string getList(String fieldName){
HttpRequest req = new HttpRequest();
req.setHeader('Authorization', 'Bearer ' + UserInfo.getSessionId());
System.debug('res------>'+UserInfo.getSessionID());
req.setHeader('Content-Type', 'application/json');
req.setEndpoint('callout:Tooling_Query/query/?q=Select+id,Namespaceprefix,developername,TableEnumOrId+FROM+customfield+Where+developername+LIKE\'' +fieldName+ '\'');
req.setMethod('GET');
Http h = new Http();
HttpResponse res = h.send(req);
System.debug('res------>'+res.getBody());
return res.getBody();
}
When I call it from lwc it returns this
[{"message":"This session is not valid for use with the REST API","errorCode":"INVALID_SESSION_ID"}]
so, how can I get session-id from lwc, I already set up a Connected App and Named Credential by the name of Tooling_Query
and add URL to remote sites.
please help me here.
You can't. Your Apex code called in a Lightning Web Components context cannot get an API-enabled Session Id. This is documented in the Lightning Web Components Dev Guide:
By security policy, sessions created by Lightning components aren’t enabled for API access. This restriction prevents even your Apex code from making API calls to Salesforce. Using a named credential for specific API calls allows you to carefully and selectively bypass this security restriction.
The restrictions on API-enabled sessions aren’t accidental. Carefully review any code that uses a named credential to ensure you’re not creating a vulnerability.
The only supported approach is to use a Named Credential authenticated as a specific user.
There is a hack floating around that exploits a Visualforce page to obtain a Session Id from such an Apex context. I do not recommend doing this, especially if you need to access the privileged Tooling API. Use the correct solution and build a Named Credential.

Blazor WASM calling Azure AAD secured Functions API

I have an Azure Functions API which uses Azure Active Directory authentication. I can test locally and deployed using a browser and curl calls in a process of:
Get a code
Use the code to get a token
Pass the token to authenticate and get the function result.
I now want to call this API from my Blazor WASM app but I'm sure there must be a nice MSAL call to do all the authentication but I cannot find any documentation on what that might be.
Does anyone have a code snippet to illustrate what needs to happen?
Further Information
My Azure Functions App and Blazor WASM client are not part of the same project and are hosted on different sub-domains of Azure hypotheticalapi.azurewebsites.net and hypotheticalweb.azurewebsites.net.
The web client application registration has API Permissions for the API and the API has an application registration which exposes itself with the scope that the client app has permissions for.
Again, the API and Web app work individually. I just don't seem able to get them to talk.
I have been following the "ASP.NET Core Blazor WebAssembly additional security scenarios" documentation but after several attempts I keep coming back to the error:
Microsoft.JSInterop.JSException: invalid_grant: AADSTS65001:
The user or administrator has not consented to use the application with ID 'e40aabb0-8ed5-4833-b50d-ec7ca4e07996' named 'BallerinaBlazor5Wasm'.
Send an interactive authorization request for this user and resource.
Even though I have revoked/deleted the client's permissions on the API, it has never repeated asking for consent. Is there a way I should clear the consent I previously gave? No idea how I might do that.
This GitHub Issue appears to be relevant.
I was stuck for the last two weeks with the same error code in the same setting: Blazor WASM talking to an AAD secured Azure Functions app.
What appeared to be a problem in my case was the scopes that I was listing in the http request when contacting AAD identification provider endpoints. Almost all examples I came across use Microsoft Graph API. There, User.Read is the scope that is given as an example. My first though was that even when I am contacting my own API I have to include the User.Read scope in the request because I was reasoning that this scope is necessary to identify the user. However, this is not the case and the only scope that you have to list when you call the authorize and token endpoints is the one that you exposed under the "Expose an API blade" in your AAD app registration.
I am using the OAuth2 authorization code in my example and not the implicit grant. Make sure that in the manifest of your API registration you have set "accessTokenAcceptedVersion": 2 and not "accessTokenAcceptedVersion": null. The latter implies the use of implicit flow as far as I know.
The scope the I exposed in my API is Api.Read. You can expose more scopes if you need but the point is that you only ask for scopes that you exposed.
I also have both following options unticked (i.e. no implicit flow). However, I tried with selecting "ID token" and it still worked. Note that the "ID token" option is selected by default if you let the Azure Portal create your AAD app registration from your function app Authentication blade.
Blazor code
Program.cs
This code has to be added.
builder.Services.AddScoped<GraphAPIAuthorizationMessageHandler>();
builder.Services.AddHttpClient("{NAME}",
client => client.BaseAddress = new Uri("https://your-azure-functions-url.net"))
.AddHttpMessageHandler<GraphAPIAuthorizationMessageHandler>();
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()
.CreateClient("{NAME}"));
builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
// NOTE: no "api://" when providing the scope
options.ProviderOptions.DefaultAccessTokenScopes.Add("{you API application id}/{api exposed scope}");
});
appsetting.json
"AzureAd": {
"Authority": "https://login.microsoftonline.com/{aad tenant id}",
"ClientId": "{application id of your blazor wasm app}",
"ValidateAuthority": true
}
GraphAPIAuthorizationMessageHandler.cs
Note that this class can have a different name. you'll then also reference a different name in Program.cs.
public class GraphAPIAuthorizationMessageHandler : AuthorizationMessageHandler
{
public GraphAPIAuthorizationMessageHandler(IAccessTokenProvider provider,
NavigationManager navigationManager)
: base(provider, navigationManager)
{
ConfigureHandler(
authorizedUrls: new[] { "https://your-azure-functions-url.net" },
// NOTE: here with "api://"
scopes: new[] { "api://{you API application id}/{api exposed scope}" });
}
}
I hope this works. If not, let me know.
At least you need to get the access token, then use the token to call the function api. In this case, if you want to get the token in only one step, you could use the client credential flow, MSAL sample here, follow every part on the left to complete the prerequisites.
The following are the approximate steps(for more details, you still need to follow the sample above):
1.Create a new App registration and add a client secret.
2.Instantiate the confidential client application with a client secret
app = ConfidentialClientApplicationBuilder.Create(config.ClientId)
.WithClientSecret(config.ClientSecret)
.WithAuthority(new Uri(config.Authority))
.Build();
3.Get the token
string[] scopes = new string[] { "<AppId URI of your function related AD App>/.default" };
result = await app.AcquireTokenForClient(scopes)
.ExecuteAsync();
4.Call the function API
httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
// Call the web API.
HttpResponseMessage response = await _httpClient.GetAsync(apiUri);
...
}

Retrieving Emails from Office365 using OAuth2.0

I am looking for a complete Step-by-step to accomplish the following objective. I have been looking around and trying many, many ways, but not one of them works.
Objective: using C# .net core code (in Azure Functions) connect to a shared mailbox of Office 365 to retrieve emails [with date filter] to do some processing, then move that email from Inbox into another folder (Processed). I want to use MailKit (or something similar
that is free and has MIT license) to retrieve, load and parse the emails.
What I have done, but of course, I can be way off…
In Azure Active Directory, I have created an App Registration
In API Permissions, I have added and granted (I am an admin) a lot of permissions (everything I guess may be related to this, from
Email, to IMAP, SMTP, POP, Application, User and a bunch of other permissions.
In Certificates & Secrets, I created a client secret and recorded the secret (Value)
From Overview, I recorded the Client ID, Tenant ID, etc.
Code (I tried several variation of this…)
string[] scopes = {"https://graph.microsoft.com/.default" }
/*
for scopes, I have also tried:
“https://graph.microsoft.com/IMAP.AccessAsUser.All”
“https://outlook.office.com/IMAP.AccessAsUser.All”
*/
var authority = authority + tenant;
/*
for authority, I have also tried:
“https://login.microsoftonline.com/“
“https://login.microsoftonline.com/common”
“https://login.microsoftonline.com/oauth2”, etc…
*/
var client = ConfidentialClientApplicationBuilder
.Create(clientID)
.WithClientSecret(secret)
.WithAuthority(new Uri(authority))
.Build();
/* Fails every time! */
CancellationToken cancellationToken = default;
var authResult = await app.AcquireTokenForClient(scopes)
.ExecuteAsync(cancellationToken);
/* MailKit to retrieve emails… */
/*
any step-by-step code using MailKit to
accomplish the Objective would be much appreciated.
*/
Firstly, you should not use this method to get the access token.
var client = ConfidentialClientApplicationBuilder
.Create(clientID)
.WithClientSecret(secret)
.WithAuthority(new Uri(authority))
.Build();
This method is using client credential flow which is not supported to use IMAP.AccessAsUser.All delegated permission.
This method mentioned by jstedfast is using Interactive provider. The interactive flow is used by mobile applications (Xamarin and UWP) and desktops applications to call Microsoft Graph.
So if configuring "http://localhost" as a Public client (mobile & desktop) redirect URI for your application doesn't work, I don't think you could implement it in the C# .net core Azure Function app. Azure Function app doesn't support login interactively within it. You can only use it in a console app.
But there are 2 workarounds which allow you to get the user access token for Microsoft Graph.
Implement Username/password provider to generate the client and access token. But it is using ROPC flow which is not recommended by Microsoft. See details on Warning tip.
Configure additional Login Params in App service to get the access token to call Microsoft Graph. Please refer to CONFIGURING AN APP SERVICE TO GET AN ACCESS TOKEN FOR AAD GRAPH API. The key point is changing the additionaloginparams to the following [“response_type=code id_token”, “resource=https://graph.microsoft.com”]. Related official document here.

How can you get the Google+ Profile of the current user when using Google Cloud Endpoint's (Java) built in authentication?

My Setup
Backend: Google App Engine (Java) w/ Google Cloud Endpoints using Endpoint's built in authentication
Frontend: AngularJS web app
Problem
I need to get the Google+ profile for my users. The keyword "me" can generally be used to get the current user's Google+ profile, however since all the authentication, in Google Cloud Endpoints, is done under the hood, I don't see anyway to get credentials, nor a token, for the current user. All you get it the com.google.appengine.api.users.User object.
Is there any way to get user credentials, or the access token, when using Google Cloud Endpoint's built in authentication?
Note: Google+ profile ID is different form Google account ID.
Possible Solution
I could just use the Google+ JS client with the keyword "me" and have the user send their Google+ ID and then subsequently store it and tie it to their Google Account ID, but this would be incredible insecure as the user could hack their way to sending the ID of any Google+ account.
It is possible to get the user access token when using Google Cloud Endpoint's built in authentication.
Add the parameter HttpServletRequest request to your Google Cloud endpoint as shown below. This will allow you to get the raw request.
You will then need to retreive the header called Authentication. This will get a Bearer access token that will allow you to build credentials to impersonate the authenticated user.
Next you will use that Bearer access token to build a com.google.api.client.googleapis.auth.oauth2.GoogleCredential object. You will need this to build the Plus service.
Use the Plus builder to build a Plus service object with the credential you just created.
Sample Code
#ApiMethod(path = "myPath")
public void myEndpoint(HttpServletRequest request, ParmOne paramOne, ...) throws OAuthRequestException {
if (user == null) {
throw new OAuthRequestException("Authentication error!");
}
GoogleCredential credentialAsUser = new GoogleCredential().setAccessToken(request.getHeader("Authorization").substring(7)); // Start string at index position 7 to remove prefix "Bearer" from token.
Plus plus = new Plus.Builder(new UrlFetchTransport(), new JacksonFactory(), credentialAsUser).setApplicationName("my-app").build();
Person profile = plus.people().get("me").execute();
}
Documentation
The Java docs for the Google Plus client can be found here.
The Java docs for instructions on creating Google credentials can be found here.
Additional Answer for Android Clients
Problem
In addition to the Marc's answer it is important that the GoogleCredentials-Object needs an access_token in the request-header.
If you call the endpoint with your apiExplorer or a javascript endpoint, this token is already served in the Authorization-header. But if you follow the docs for an android client your requests header contains an id_token, so GoogleCredentials.setAccessToken does not work.
Solution
To change the type of authorization to an access_token simply create your GoogleAccountCredential-Object in Android with usingOAuth2 instead of usingAudience.
Example
Replace this code in your Android App
credential = GoogleAccountCredential.usingAudience(this,
"server:client_id:1-web-app.apps.googleusercontent.com");
with this
credential = GoogleAccountCredential.usingOAuth2(this,
Collections.singleton(Scopes.PLUS_LOGIN));
and send it to your Api, as it is explained by the documentation
Helloworld.Builder helloWorld = new Helloworld.Builder(AppConstants.HTTP_TRANSPORT,
AppConstants.JSON_FACTORY,credential);

How do I protect my API that was built using Google Cloud Endpoints?

The API is a backend to a mobile app. I don't need user authentication. I simply need a way to secure access to this API. Currently, my backend is exposed.
The documentation seems to only talk about user authentication and authorization, which is not what I need here. I just need to ensure only my mobile app can talk to this backend and no one else.
Yes, you can do that: use authentication to secure your endpoints without doing user authentication.
I have found that this way of doing it is not well documented, and I haven't actually done it myself, but I intend to so I paid attention when I saw it being discussed on some of the IO13 videos (I think that's where I saw it):
Here's my understanding of what's involved:
Create a Google API project (though this doesn't really involve their API's, other than authentication itself).
Create OATH client ID's that are tied to your app via its package name and the SHA1 fingerprint of the certificate that you will sign the app with.
You will add these client ID's to the list of acceptable ID's for your endpoints. You will add the User parameter to your endpoints, but it will be null since no user is specified.
#ApiMethod(
name = "sendInfo",
clientIds = { Config.WEB_CLIENT_ID, Config.MY_APP_CLIENT_ID, Config.MY_DEBUG_CLIENT_ID },
audiences = { Config.WEB_CLIENT_ID }
// Yes, you specify a 'web' ID even if this isn't a Web client.
)
public void sendInfo(User user, Info greeting) {
There is some decent documentation about the above, here:
https://developers.google.com/appengine/docs/java/endpoints/auth
Your client app will specify these client ID's when formulating the endpoint service call. All the OATH details will get taken care of behind the scenes on your client device such that your client ID's are translated into authentication tokens.
HttpTransport transport = AndroidHttp.newCompatibleTransport();
JsonFactory jsonFactory = new JacksonFactory();
GoogleAccountCredential credential = GoogleAccountCredential.usingAudience( ctx, Config.WEB_CLIENT_ID );
//credential.setSelectedAccountName( user ); // not specify a user
Myendpoint.Builder builder = new Myendpoint.Builder( transport, jsonFactory, credential );
This client code is just my best guess - sorry. If anyone else has a reference for exactly what the client code should look like then I too would be interested.
I'm sorry to say that Google doesn't provide a solution for your problem (which is my problem too).
You can use their API key mechanism (see https://developers.google.com/console/help/new/#usingkeys), but there is a huge hole in this strategy courtesy of Google's own API explorer (see https://developers.google.com/apis-explorer/#p/), which is a great development tool to test API's, but exposes all Cloud Endpoint API's, not just Google's services API's. This means anyone with the name of your project can browse and call your API at their leisure since the API explorer circumvents the API key security.
I found a workaround (based on bossylobster's great response to this post: Simple Access API (Developer Key) with Google Cloud Endpoint (Python) ), which is to pass a request field that is not part of the message request definition in your client API, and then read it in your API server. If you don't find the undocumented field, you raise an unauthorized exception. This will plug the hole created by the API explorer.
In iOS (which I'm using for my app), you add a property to each request class (the ones created by Google's API generator tool) like so:
#property (copy) NSString *hiddenProperty;
and set its value to a key that you choose. In your server code (python in my case) you check for its existence and barf if you don't see it or its not set to the value that your server and client will agree on:
mykey,keytype = request.get_unrecognized_field_info('hiddenProperty')
if mykey != 'my_supersecret_key':
raise endpoints.UnauthorizedException('No, you dont!')
Hope this puts you on the right track
The documentation is only for the client. What I need is documentation
on how to provide Service Account functionality on the server side.
This could mean a couple of different things, but I'd like to address what I think the question is asking. If you only want your service account to access your service, then you can just add the service account's clientId to your #Api/#ApiMethod annotations, build a GoogleCredential, and invoke your service as you normally would. Specifically...
In the google developer's console, create a new service account. This will create a .p12 file which is automatically downloaded. This is used by the client in the documentation you linked to. If you can't keep the .p12 secure, then this isn't much more secure than a password. I'm guessing that's why this isn't explicitly laid out in the Cloud Endpoints documentation.
You add the CLIENT ID displayed in the google developer's console to the clientIds in your #Api or #ApiMethod annotation
import com.google.appengine.api.users.User
#ApiMethod(name = "doIt", scopes = { Constants.EMAIL_SCOPE },
clientIds = { "12345678901-12acg1ez8lf51spfl06lznd1dsasdfj.apps.googleusercontent.com" })
public void doIt(User user){ //by convention, add User parameter to existing params
// if no client id is passed or the oauth2 token doesn't
// match your clientId then user will be null and the dev server
// will print a warning message like this:
// WARNING: getCurrentUser: clientId 1234654321.apps.googleusercontent.com not allowed
//..
}
You build a client the same way you would with the unsecured version, the only difference being you create a GoogleCredential object to pass to your service's MyService.Builder.
HttpTransport httpTransport = new NetHttpTransport(); // or build AndroidHttpClient on Android however you wish
JsonFactory jsonFactory = new JacksonFactory();
// assuming you put the .p12 for your service acccount
// (from the developer's console) on the classpath;
// when you deploy you'll have to figure out where you are really
// going to put this and load it in the appropriate manner
URL url = getClass().class.getResource("/YOURAPP-b12345677654.p12");
File p12file = new File(url.toURI());
GoogleCredential.Builder credentialBuilder = new GoogleCredential.Builder();
credentialBuilder.setTransport(httpTransport);
credentialBuilder.setJsonFactory(jsonFactory);
//NOTE: use service account EMAIL (not client id)
credentialBuilder.setServiceAccountId("12345678901-12acg1ez8lf51spfl06lznd1dsasdfj#developer.gserviceaccount.com"); credentialBuilder.setServiceAccountScopes(Collections.singleton("https://www.googleapis.com/auth/userinfo.email"));
credentialBuilder.setServiceAccountPrivateKeyFromP12File(p12file);
GoogleCredential credential = credentialBuilder.build();
Now invoke your generated client the same way
you would the unsecured version, except the builder takes
our google credential from above as the last argument
MyService.Builder builder = new MyService.Builder(httpTransport, jsonFactory, credential);
builder.setApplicationName("APP NAME");
builder.setRootUrl("http://localhost:8080/_ah/api");
final MyService service = builder.build();
// invoke service same as unsecured version

Resources