Error 400: invalid_scope "https://www.googleapis.com/auth/chat.bot" - oauth-2.0

The documentation for the new google hangouts chat says that you need to authorize the scope https://www.googleapis.com/auth/chat.bot to do pretty much anything.
Here's the error:
While generating an authentication URL using their OAuth2 client I get the message that the scope is invalid. I don't have that problem if I use https://www.googleapis.com/auth/chat or some other scope like the one for google plus.
When I try to google things on in the API Explorer no combination of the URL or parts of the URL work either.
Here is my code to fetch the URL, seems to work just fine for everything else:
var {google} = require('googleapis');
var OAuth2 = google.auth.OAuth2;
var oauth2Client = new OAuth2(
"clientid-idididid.apps.googleusercontent.com",
"_secretsuff",
"http://localhost:3000/auth/google/callback"
);
var scopes = [
"https://www.googleapis.com/auth/chat", //Works
"https://www.googleapis.com/auth/chat.bot" // Does not work
];
var url = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: scopes,
});
console.log(url);

In case others are running across this problem I think I've figured this out. Google doesn't seem need this auth scope enabled by a domain user because it's already authorised on the domain when your testing your bot. The "authorisation" of these scopes are dictated by users in a domain adding/removing bots from spaces.
I'll go into a bit of detail if you're confused.
When you create a bot in the console for an organisation https://console.cloud.google.com/apis/api/chat.googleapis.com/ your bot is added to the domain and can be added to spaces by users. If then go over to to the credentials and create a service account you can use that json file credentials to access the API as your bot. The code below gets a list of the people in a space.
var { google } = require('googleapis');
var chat = google.chat("v1");
var key = require('./google_service-account-credentials.json');
var jwtClient = new google.auth.JWT(
key.client_email,
null,
key.private_key,
['https://www.googleapis.com/auth/chat.bot'], // an array of auth scopes
null
);
jwtClient.authorize(function (err, tokens) {
chat.spaces.members.list({
auth: jwtClient,
parent: "spaces/AAAAD4xtKcE"
}, function (err, resp) {
console.log(resp.data);
});
});
If you try to get a list of members on other spaces (and other domains) the bot will fail with the exact same error message:
"Bot is not a member of the space."
I assume if you list your bot on the marketplace and it gets added to different domains and spaces google's API makes sure that your bot can do what it's trying to do on a space by space basis. It would be annoying have to setup some authentication flow after a bot has already been added for it to do its job. This is also probably why the current REST api doesn't let you list spaces under domains, it's not the paradigm this API works under.

It may have to do with one of the following:
The scope is created for service accounts. Make sure you are accessing the REST API with a service account.
Make sure that the bot is added to the room or space and has access to what you want it do.
Make sure the Service account is part of the bot project that you are using for the bot.

Related

Error when querying Microsoft Graph API Shifts: "MS-APP-ACTS-AS header needs to be set for application context requests"

We are trying to query shifts in the Microsoft Graph API using a C# app, now that StaffHub got deprecated , in the past we were getting an Unknown Error which looked like a permissions issue.
In the docs I noticed permissions for Schedule.ReadAll and Schedule.ReadWriteAll so I added them to the application permissions in our App Registration in Azure.
Now when we send the request to https://graph.microsoft.com/beta/teams/{teamid}/schedule we get this error:
Microsoft.Graph.ServiceException: 'Code: Forbidden Message: {"error":{"code":"Forbidden","message":"MS-APP-ACTS-AS header needs to be set for application context requests.","details":[],"innererror":{"code":"MissingUserIdHeaderInAppContext"}}}
The documentation says the Schedule permissions are in private preview, are these required for querying a schedule & shifts, and if so, is it possible to request access to the private preview?
I'm in the same situation. It's possible to request private preview access (we have), but I'm guessing that it's primarily granted to Microsoft partners or at least have a connection at Microsoft.
The workaround for me has been getting access on behalf of a user. It does however require the user to enter username and password in order to get an access token, so it might not be a perfect solution for you. But it works. You need to add (and, I believe, grant admin consent for) delegated permissions for this to work, either Group.Read.All or Group.ReadWrite.All.
Edit:
I've got it working now. We have private preview access, so I'm not sure this will help you unless you do too, but as I understand it will be available eventually. Given your question, I presume you already have an access token.
Add MS-APP-ACT-AS as a header with the user ID of the user you want the Graph client to act as.
If you're using the Graph SDK for .NET Core you can just add a header to the authentication provider:
public IAuthenticationProvider GetAuthenticationProviderForActingAsUser(string userId, string accessToken)
{
return new DelegateAuthenticationProvider(
requestMessage =>
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
// Get event times in the current time zone.
requestMessage.Headers.Add("Prefer", "outlook.timezone=\"" + TimeZoneInfo.Local.Id + "\"");
requestMessage.Headers.Add("MS-APP-ACTS-AS", userId);
return Task.CompletedTask;
});
}
Then you call the graph service client:
var authenticationProvider = GetAuthenticationProviderForActingAsUser(userId, accessToken);
var graphClient = new GraphServiceClient(authenticationProvider);
You should then be able to fetch the shifts:
var shifts = await graphClient.Teams[teamId].Schedule.Shifts
.Request()
.AddAsync(shift);

How do you use an iOS Google ID Token to fetch a user's full profile from the backend (Node JS)?

I'm using the Google Login iOS SDK to login, then passing GIDGoogleUser.authentication.idToken to the server, which I'm then verifying in Node JS. The verification in the code below works fine. "payload" var ends up being correct with basic information about the user.
How do I translate the idToken into credentials that I can use to git the people.get endpoint? (I want to know whether the user is using the default Google profile photo or not, and that is available from the people.get endpoint.) This does not seem to be documented anywhere.
https://developers.google.com/people/api/rest/v1/people/get
var auth = new GoogleAuth;
var client = new auth.OAuth2(GoogleUtils.clientIDs, '', '');
client.verifyIdToken(
token,
GoogleUtils.clientIDs,
// Or, if multiple clients access the backend:
//[CLIENT_ID_1, CLIENT_ID_2, CLIENT_ID_3],
function(e, login) {
if (e) {
return next(e, null);
}
var payload = login.getPayload();
return next(null, payload);
});
Thanks for your help. I can't seem to find any of this info in the documentation. Google's APIs are very poorly documented it seems.
Unfortunately, as noted, the current ID token payload does not say whether the photo is the default one (probably something we should add). If you need an access token to call Google's REST APIs (such as people.get) for more user data, then you need to obtain an OAuth auth code, and exchange it for access and refresh tokens, as documented at https://developers.google.com/identity/sign-in/ios/offline-access

jwt authentication in iOS client nodejs server via third party authenticator

I am trying to wrap my head around using json webtoken (jwt) based authentication on a server coupled to using a third party (say google) to authenticate the user. Originally I've managed to build my own login and jwt handling scheme with jsonwebtoken on my nodejs server, but we need a client running on an iOS system to interact with it and started looking around for a solution where we don't have to code so much client code (requesting new token when expired etc.) and thought that we would use a third party library to do this for us.
The thing is I did not find anything that would do this for us. I found libraries that could handle connecting the client to a google api for the client, I found user identification handled by google, but didn't find anything that would handle actually getting a jwt that the server would except as a genuine user.
My question is essentially this: we have an iOS client and a nodejs server and would like to use google to authenticate our users and have the client call api-s on our nodejs server, with as much of the authentication process handled by some third party library (google's?), how should we get around to this?
As a note, I've seen passport but that seems to operate with sessions only, and I would have to solve the jwt handling by myself were I to use that.
The iOS part is not ready, but I managed to use google to authenticate and authorize without a session in the browser. The idea is, that the client logs in to google (see here for web app) and google graciously also gives you a token with the login, which will be good for the server. On the nodejs side I used passport and the google-id-token strategy (see on github). There are quite a few strategies for google out there, but this one works. Although, this has a shortcoming, it can't accept the token in the header, but I fixed that in a pull request (see here).
Since I had a bit of a problem of how to use the User.findOrCreate part of all the passport examples, I'll put in my code here that covers a full working example:
var passport = require('passport');
var GoogleTokenStrategy = require(passport-google-id-token)
passport.use(new GoogleTokenStrategy({
clientID: config.googleAuth.clientID,
clientSecret: config.googleAuth.clientSecret,
},
function(parsedToken, googleId, done) {
console.log(parsedToken);
console.log(googleId);
User.findOne({ 'google.id': googleId }, function (err, user) {
if (!user) {
var testuser = new User({
name: parsedToken.payload.name,
givenName : parsedToken.payload.givenName,
familyName : parsedToken.payload.familyName,
nameunderscore : parsedToken.payload.name.split(' ').join("_"),
admin: false,
email: parsedToken.payload.email,
settings: {save_folder:"default"},
'google.id' : googleId,
'google.email' : parsedToken.payload.email,
});
testuser.save(function(err) {})
}
return done(err, user);
});
}
));
User comes from mongodb in a separate js:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
module.exports = mongoose.model('User', new Schema({
name: String,
nameunderscore : String,
givenName: String,
familyName: String,
admin: Boolean,
settings: {
save_folder: String
},
email: String,
google: {
id: String,
email: String
}
}));
And this is how I added the passport strategy to a router (note that session is set to false):
var apiRoutes = express.Router();
apiRoutes.use(passport.authenticate('google-id-token',{ session: false }));
Now every call to any route in apiRoutes must send on id_token with a valid google token to get access.

Google My Business Requested entity was not found

I'm trying to implement an integration between a ERP system and Google My Business to keep store data in sync.
I have a project in the developer console. I have gained access to the GMB API and is approved by Google to use this API.
I'm using a serviceaccount and have followed the instructions from various guides.
But now I'm stuck.
I'm using the google GMB c# library to connect to GMB. I have a valid .12 file for my service account.
string MybusinessServiceScope = "https://www.googleapis.com/auth/plus.business.manage";
String serviceAccountEmail = "myserviceaccount#myapplication-1349.iam.gserviceaccount.com";
var certificate = new X509Certificate2(_serviceP12File, "notasecret", X509KeyStorageFlags.Exportable);
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = new[] { MybusinessServiceScope },
}.FromCertificate(certificate));
return new MybusinessService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "myapplication-1349",
});
When I try to List, Patch or Create locations I keep getting the same response:
Requested entity was not found. [404]
Errors [
Message[Requested entity was not found.] Location[ - ] Reason[notFound] Domain[global]
]
Any help is appreciated
I was unable to make this work with a service account. After talking to google support on the matter I change to use the OAuth application flow instead.
This works.
My chat with google: https://www.en.advertisercommunity.com/t5/Google-My-Business-API/Unable-to-PATCH-location-with-v3/td-p/579536#
Hope this can help others
this error occurs when the user doesn't have access to the provided location. (invalid access token or user access has been revoked etc)

Multiple Twitter accounts using Google App Script

I'm experimenting with Google Apps Script and Twitter, and I'd like to be able to access multiple Twitter accounts through one spreadsheet. At the moment I've attempted the approach below (a unique OAuthService name for each Twitter account), and this kind-of works but it clunky because I have to randomly authorize one account (and not more than one) each time the script is run, and the popup dialog doesn't tell me which account (i.e. id) I'm authenticating for.
Ideally, I'd like to force each user to give Twitter permission on first use, then store that token for later use - is this possible withe Google App Script?
Thanks.
function oAuth(id) {
var oauthConfig = UrlFetchApp.addOAuthService(NS_TWITTER + id);
oauthConfig.setAccessTokenUrl("https://api.twitter.com/oauth/access_token");
oauthConfig.setRequestTokenUrl("https://api.twitter.com/oauth/request_token");
oauthConfig.setAuthorizationUrl("https://api.twitter.com/oauth/authorize");
oauthConfig.setConsumerKey(CONSUMER_KEY);
oauthConfig.setConsumerSecret(CONSUMER_SECRET);
};
and then
var options =
{
"method": "GET",
"oAuthServiceName":NS_TWITTER + id,
"oAuthUseToken":"always",
};
try {
var result = UrlFetchApp.fetch(feed, options);
}
Yes, is possible.
To store the token values use userProperties (Docs here) or CacheService wich remains for 20 minutes in cache (Docs here).
Example storing token using UserProperties
UserProperties.setProperty('token', 'value');
var token = UserProperties.getProperty('token');
Example storing token using CachService
// Gets a cache that is private to the current user
var cache = CacheService.getPrivateCache();
cache.put('token', 'value');
var token = cache.get('token');
After building you cache solution you need to check if the token is valid with twitter API. If it's invalid you should require the auth again.

Resources