google app script consumer with google appEngine Provider (Oauth) - google-app-engine

I could get an Oauth conexion between GAS and GAE.
I built a Google Gadget which needs some data from my datastore application, it has 3 end points to finally get an access token.
http//[myapp].appspot.com/_ah/OAuthGetRequestToken
http//[myapp].appspot.com/_ah/OAuthAuthorizeToken
http//[myapp].appspot.com/_ah/OAuthGetAccessToken
In GAS side i have the tipical function to get an access token.
function oauthTokenFetch(){
var oAuthConfig = UrlFetchApp.addOAuthService("myAppName");
oAuthConfig.setAccessTokenUrl("https://<myApp>appspot.com/_ah/OAuthGetAccessToken");
oAuthConfig.setRequestTokenUrl("https://<myApp>.appspot.com/_ah/OAuthGetRequestToken");
oAuthConfig.setAuthorizationUrl("https://<myApp>.appspot.com/_ah/OAuthAuthorizeToken");
oAuthConfig.setConsumerKey("<myApp>.appspot.com");
oAuthConfig.setConsumerSecret("myConsumerSecret");
var requestData = {
"method": "GET",
"oAuthServiceName": "myAppName",
"oAuthUseToken": "always"
};
try {
var response2=UrlFetchApp.fetch("http://<myApp>.appspot.com/test/oauth",requestData);
Logger.log(response2.getResponseCode());
}catch(exception){
Logger.log(exception);
}
If i revoked the access token on google accounts, it isn't able for getting another one, the popup which grants the authorization don't appears ... i have to copy the google gadget and do the authorization again.
Somebody have a Solution?

There currently isn't a method to allow you to revoke or remove an OAuth token in a script. It looks like you already filed a bug/feature request on the topic, and we'll follow up there.

Related

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);
...
}

How does AAD API Access delegate permission work?

I'm having a little trouble following how API Access delegate permissions work with azure active directory. I feel like i'm probably misunderstanding a key aspect of how AAD works.
Here is my set up
I have a Web Application let’s call it WebApp. I have created
an AAD for the Web Application and registered with a AAD App ID. Let’s
call it App ID A
I have a Web Api let’s call it ApiService. I have also created an AAD for it and registered with a AAD App ID. Let’s all it App ID B.
In AAD App ID A, I have updated the clicked on the API Access ->
Required Permissions -> Add (App ID B ; Web API) permissions
I’ve updated the manaifest in the AAD App ID B, to give consent to
knownClientApplications to include the client ID of the Web App
I’ve also enable oauth2AllowImplicitFlow to be true for both App’s
manifest.
What I’m trying to do is, A user signs into the web application sign. When it signs in, the user is able to acquire a token for the specific Web App App ID A. The user should be able to use that token and have access the Api Service with App ID B. I thought by configuring the whole API Access -> Required Permissions within the Web Application it would give me delegate permission with the logged in user to communicate with the Api Service WebApi.
When I examine the JWT token, I notice that there is a claim for Microsoft Graph, but not for the ApiService. Shouldn’t I be seeing a claim?
When I try to use the token, it reacts with a 404 authentication error.
Any advice appreciated,
Thanks,
Derek
UPDATE
In response to #joonasw
I actually looked at the example you wrote when i started.
https://joonasw.net/view/aspnet-core-2-azure-ad-authentication
In the example, the web application is initialized with:
.AddOpenIdConnect(opts =>
{
Configuration.GetSection("OpenIdConnect").Bind(opts);
opts.Events = new OpenIdConnectEvents
{
OnAuthorizationCodeReceived = ctx =>
{
return Task.CompletedTask;
}
};
});
In the HomeController, there is code to retrieve the token for the graph api
private async Task<string> GetAccessTokenAsync()
{
string authority = _authOptions.Authority;
string userId = User.FindFirstValue("http://schemas.microsoft.com/identity/claims/objectidentifier");
var cache = new AdalDistributedTokenCache(_cache, _dataProtectionProvider, userId);
var authContext = new AuthenticationContext(authority, cache);
//App's credentials may be needed if access tokens need to be refreshed with a refresh token
string clientId = _authOptions.ClientId;
string clientSecret = _authOptions.ClientSecret;
var credential = new ClientCredential(clientId, clientSecret);
var result = await authContext.AcquireTokenSilentAsync(
"https://graph.microsoft.com",
credential,
new UserIdentifier(userId, UserIdentifierType.UniqueId));
return result.AccessToken;
}
From my understanding, when the user initially login to the web application it will trigger the OnAuthorizationCodeReceived() method where it will be using the clientId/clientSecret/resource of the web applicaiton. The token is stored in the distributed token cache under the key resource/client id.
In the example, GetAccessTokenAsync() is used to grab the token to access the graph API.
In my case, I was hoping to update that method to retrieve the token for the WebApi which has a different clientId/clientSecret/resoruce. In my case, it will AcquireTokenSilentAsync will throw an AdalTokenAcquisitionExceptionFilter because the token needed is not stored in the cache and in the AdalTokenAcquisitionExceptionFilter it will call try to reauthenticate
context.Result = new ChallengeResult();
which will redirect to the authentication page and then hits the AddOpenIdConnect() method. However, the openIdConnect is configured with the web app clientID/ClientSecret/Resource and will not store the new token properly. It will try to call GetAccessTokenAsync() again and the whole process will go in an infinite loop.
In the example, if you were to comment out the "Anthentication:resource" in app.settings, you will experience the same issue with the infinite loop. What happens is that you initially authenticate correctly with no resource specified. Then when you click on you try to get the token for microsoft graph which is a new resource, it can't find it in the cache and then tries to reauthenticate over and over again.
I also notice that the acquireAsyncAuthentication only returns a AuthenticationResult with a bearer tokentype. How would you get the refresh token in this case?
Any advice?
Thanks,
Derek
UPDATE (Solution)
Thanks to #jaanus. All you have to do is update the resource to the clientid of the web api and pass that into AcquireTokenSilentAsync. The web api id uri that you can get from the azure portal did not work.
Okay, so it seems there are multiple questions here. I'll try to make some sense of this stuff to you.
Adding the "Web App"'s client id to the "ApiService" knownClientApplications is a good idea.
It allows for consent to be done for both apps at the same time. This really only matters for multi-tenant scenarios though.
Now, your Web App will be acquiring access tokens at some point.
When it does, it must specify a resource parameter.
This parameter says to AAD which API you wish to call.
In the case of the "ApiService", you should use either its client id or Application ID URI (this is more common).
Depending on the type of your Web App, the access token is acquired a bit differently.
For "traditional" back-end apps, the Authorization Code Grant flow is usually used.
In this flow your back-end gets an authorization code after the user logs in, and your Web App can then exchange that code for the access token.
In the case of a front-end JavaScript app, you would use the Implicit Grant flow, which you have allowed (no need to enable it in the API by the way).
This one allows you to get access tokens directly from the authorization endpoint (/oauth2/authorize) without talking to the token endpoint as you usually have to.
You can actually get the access token right away after login in the fragment of the URL if you wish.
ADAL.JS makes this quite a lot easier for you if you are going in this route.
The reason you get the authentication error is because the access token is probably meant for Microsoft Graph API. You need to request an access token for your API.
An access token is always only valid for one API.

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);

Accessing a Google Drive spreadsheet from Appengine

I have an appengine app that needs to access a single, hard-coded spreadsheet on Google Drive.
Up until now I have been achieving this as follows:
SpreadsheetService service = new SpreadsheetService("myapp");
service.setUserCredentials("myusername#gmail.com", "myhardcodedpassword");
When I tried this today with a new user, I got InvalidCredentialsException even though the username and password were definitely correct. I got an email in my inbox saying suspicions sign-ins had been prevented, and there seems to be no way to enable them again.
I am also aware that hardcoding passwords in source is bad practice.
However, I have read very widely online for how to enable OAuth/OAuth2 for this, and have ended up wasting hours and hours piecing fragments of information from blogs, stackoverflow answers etc, to no avail.
Ideally the solution would involve an initial process to generate a long-lived access token, which could then be hard-coded in to the app.
I want a definitive list of steps for how to achieve this?
EDIT: As Google have redesigned the API Console, the details of the steps below have changed - see comments
OK here goes, step by step
Go to Google Cloud Console and register your project (aka application)
You need to note the Client ID, and Client Secret
Go to the OAuth Playground, click the gear icon and choose and enter your own credentials
You will be reminded that you need to go back to the Cloud COnsole and add the Oauth Playground as a valid callback url. So do that.
Do Step 1, choosing the spreadsheet scope and click authorize
Choose your Google account if prompted and grant auth when prompted
Do Step 2, Click 'Exchange auth code for tokens'
You will see an input box containing a refresh token
The refresh token is the equivalent of your long lived username/password, so this is what you'll hard code (or store someplace secure your app can retrieve it).
When you need to access Google Spreadsheets, you will call
POST https://accounts.google.com/o/oauth2/token
content-type: application/x-www-form-urlencoded
client_secret=************&grant_type=refresh_token&refresh_token=1%2xxxxxxxxxx&client_id=999999999999.apps.googleusercontent.com
which will return you an access token
{
"access_token": "ya29.yyyyyyyyyyyyyyyyyy",
"token_type": "Bearer",
"expires_in": 3600
}
Put the access token into an http header for whenever you access the spreadsheet API
Authorization: Bearer ya29.yyyyyyyyyyyyyyyyy
And you're done
Pinoyyid has indeed provided wonderful help. I wanted to follow up with Python code that will allow access to a Personal (web-accessible) Google Drive.
This runs fine within the Google App Engine (as part of a web app) and also standalone on the desktop (assuming the Google App Engine SDK is installed on your machine [available from: https://developers.google.com/appengine/downloads]).
In my case I added https://www.googleapis.com/auth/drive scope during Pinyyid's process because I wanted access to all my Google Drive files.
After following Pinoyyid's instructions to get your refresh token etc., this little Python script will get a listing of all the files on your Google drive:
import httplib2
import datetime
from oauth2client.client import OAuth2Credentials
from apiclient.discovery import build
API_KEY = 'AIz...' # from "Key for Server Applications" from "Public API Access" section of Google Developers Console
access_token = "ya29..." # from Piinoyyid's instructions
refresh_token = "1/V..." # from Piinoyyid's instructions
client_id = '654....apps.googleusercontent.com' # from "Client ID for web application" from "OAuth" section of Google Developers Console
client_secret = '6Cl...' # from "Client ID for web application" from "OAuth" section of Google Developers Console
token_expiry = datetime.datetime.utcnow() - datetime.timedelta(days=1)
token_uri = 'https://accounts.google.com/o/oauth2/token'
user_agent = 'python urllib (I reckon)'
def main():
service = createDrive()
dirlist = service.files().list(maxResults=30)
print 'dirlist', dirlist
result = dirlist.execute()
print 'result', result
def createDrive():
credentials = OAuth2Credentials(access_token, client_id, client_secret, refresh_token, token_expiry, token_uri, user_agent)
http = httplib2.Http()
http = credentials.authorize(http)
return build('drive', 'v2', http=http, developerKey=API_KEY)
main()
I'm grateful to all who have provided the steps along the way to solving this.

Retrieve a document from a google Account with OAuth

I need to know how to obtain documents from a specific user, the user is authenticated using ...
UserService userService = UserServiceFactory.getUserService();
User user = userService.getCurrentUser();
When the user is authenticated , I need to get the documents from google docs. I know I have to use OAuth, but I could not use it correctly.
I hope some one can help me, Thanks.
if i have already a user logged in how can i get his docs
Here is a good reference with way more information than I could provide myself:
http://code.google.com/apis/documents/docs/3.0/developers_guide_java.html
That should get you on the right path.
The user has authenticated to App Engine, but they have not authenticated against any other services, or provided you with access to them. You cannot use their App Engine credentials to access any other services; you will need to follow the standard OAuth authorization procedure in order to access any other services owned by that user.
first do the oauth part using for example signpost library : here is an example
( look at there sample code for Google)
after you complete oauth process in step 1; and store the ACCESS_TOKEN and
TOKEN_SECRET you can access google docs service like code snippet below .
(note : CONSUMER_KEY & CONSUMER_SECRET are the ones you get from google and used in step 1 . ACCESS_TOKEN & TOKEN_SECRET are the secret access tokens sent to you by google auth server after you complete oauth process in step 1)
hope this helps.
GoogleOAuthParameters oauthParameters = new GoogleOAuthParameters();
oauthParameters.setOAuthConsumerKey(CONSUMER_KEY);
oauthParameters.setOAuthConsumerSecret(CONSUMER_SECRET);
oauthParameters.setOAuthToken(ACCESS_TOKEN);
oauthParameters.setOAuthTokenSecret(TOKEN_SECRET);
DocsService client = new DocsService("yourCompany-YourAppName-v1");
client.setOAuthCredentials(oauthParameters, new OAuthHmacSha1Signer());
URL feedUrl = new URL("https://docs.google.com/feeds/default/private/full");
DocumentListFeed resultFeed = client.getFeed(feedUrl, DocumentListFeed.class);
for (DocumentListEntry entry : resultFeed.getEntries()) {
System.out.println(entry.getTitle().getPlainText());
}

Resources