I am having an issue authenticating through the Google API system using the googleapis library. I have tried multiple other libraries as well without success.
The error I am getting can be seen below. It is
TypeError: Bad input string
I have a .json file that was created through the Google API Console, the service account has all permissions available as well as domain wide delegation. I have tried it without DwD as well which didn't work either.
The code I am using to authenticate is as follows:
let { google } = require('googleapis');
let privatekey = require('../config/keys/CCKey.json');
let jwtClient = new google.auth.JWT(
privatekey.client_email,
null,
privatekey.private_key,
['https://www.googleapis.com/auth/analytics']);
//authenticate request
jwtClient.authorize(function (err, tokens) {
if (err) {
console.log(err);
return;
} else {
console.log("Successfully connected!");
}
});
The JSON file that was generated by the Google API Console is as follows:
{
"type": "service_account",
"project_id": "xxx-1xx70xxxx4xx6",
"private_key_id": "xxxxxxxxx",
"private_key": "-----BEGIN PRIVATE KEY-----\nxxxxx\n-----END PRIVATE KEY-----\n",
"client_email": "xxxx-service-account#xxxx-1xxx6.iam.gserviceaccount.com",
"client_id": "1xxxx2",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://accounts.google.com/o/oauth2/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/xxxxx"
}
I believe your problem might be to do with setting Managing API Client Access on your G Suite domain. This will need to be set by someone with Super Admin Access.
For some further information see the following link:
Perform G Suite Domain-Wide Delegation of Authority
And browse down to the section Delegate domain-wide authority to your service account
In your case, the Client Name would be your project_id from your service account JSON (xxx-1xx70xxxx4xx6) and you would need to set your scopes to https://www.googleapis.com/auth/analytics
Related
i am trying to implement a single sign on for my web application. I am using gravitee.io for the access managment and token generation.
I followed the steps in gravitees quickstart tutorial and i am now at the point that i want to verify my id_token.
In order to do that i am using the node-jsonwebtoken library. i am using total.js for my backend (which should not be as important, but i still wanted to mention it).
What i have done so far.
i have my client-id and my client-secret as well as my domain secret in the total.js config file
./configs/myconfig.conf (key/secret is changed)
url : https://sso.my-graviteeInstance.com/am
client-id : myClientId
client-secret : uBAscc-zd3yQWE1AsDfb7PQ7xyz
domain : my_domain
domain-public-key : EEEEEB3NzaC1yc2EAAAADAQABAAABAQCW4NF4R/sxG12WjioEcDIYwB2cX+IqFJXF3umV28UCHZRlMYoIFnvrXfIXObG7R9W7hk6a6wbtQWERTZxJ4LUQnfZrZQzhY/w1u2rZ3GEILtm1Vr1asDfAsdf325dfbuFf/RTyw666dFcCcpIE+yUYp2PFAqh/P20PsoekjvoeieyoUbNFGCgAoeovjyEyojvezxuTidqjaeJvU0gU4usiiDGIMhO3IPaiAud61CVtqYweTr2tX8KabeK9NNOXlTpLryBf3aTU1iXuU90mijwXZlmIzD28fWq+qupWbHcFZmmv3wADVddnxZHnFIN7DHGf5WVpb3eLvsGkIIQpGL/ZeASDFa
i added a model to handle the login workflow for total.js in order to get the jwt tokens from gravitee by REST-call.
So far everything works as expected. a session is created and stores the response in it. the gravitee response is the expected json which looks like this
{
access_token: 'some-long-token',
token_type: 'bearer',
expires_in: 7199,
scope: 'openid',
refresh_token: 'another-long-token',
id_token: 'last-long-token'
}
I split up the tokens in seperate cookies because when i tried to save them as a single cookie, i got an error that told me the cookie exceeds the 4096 length limit.
So far everything works just fine. in the frontend ajax call the success callback will be executed, just setting the window.location.href='/'; to call the dashboard of my application. I set this route to be accessible only when authorized, so that when my dashboard is called, the onAuthorize function is called by totaljs.
F.onAuthorize = function (req, res, flags, callback) {
let cookie = mergeCookies(req.cookie);
// Check the cookie length
if (!cookie || cookie.length < 20) {
console.log(`cookie not defined or length to low`);
return callback(false);
}
if (!cookie) {
console.log(`cookie undefined`);
return callback(false);
}
// Look into the session object whether the user is logged in
let session = ONLINE[cookie.id];
if (session) {
console.log(`there is a session`);
// User is online, so we increase his expiration of session
session.ticks = F.datetime;
jwt.verify(
session.id_token,
Buffer.from(CONFIG('client-secret')).toString('base64'),
function(err, decoded){
if (err) {
console.log(`jwt verify error: ${err}`);
return callback(false);
}
console.log(`decoded token user id: ${decoded.sub}`);
return callback(true, session);
})
}
console.log(`false`);
callback(false);
};
I also tried to just send the CONFIG('client-secret') without buffering. I also tried to send the CONFIG('domain-public-key'). But the error i get is always the same:
jwt verify error: JsonWebTokenError: invalid algorithm
When i copy and paste the id_token into the debugger at jwt.io with algorithm beeing set to RS256 i'll see the following decoded values:
// header
{
"kid": "default",
"alg": "RS256"
}
// payload
{
"sub": "some-generated-id",
"aud": "myClientId",
"updated_at": 1570442007969,
"auth_time": 1570784329896,
"iss": "https://sso.my-graviteeInstance.com/am/my_domain/oidc",
"preferred_username": "myUsername",
"exp": 1570798729,
"given_name": "Peter",
"iat": 1570784329,
"family_name": "Lustig",
"email": "peter.lustig#domain.com"
}
i copied the public key from my domain in to the respective textfield and i also tried to use the client-secret. no matter what i do, the error i am getting here is
Warning: Looks like your JWT signature is not encoded correctly using
base64url (https://www.rfc-editor.org/rfc/rfc4648#section-5). Note that
padding ("=") must be omitted as per
https://www.rfc-editor.org/rfc/rfc7515#section-2
I dont understand why there is an algorithm error when i try to verify the token in my backend and some encoding error at jwt.io debugger.
can somebody explain to me on how to fix the issue? Thanks in advance
Pascal
edit: changed title
My app makes use of Firebase to log users in, and they have access to a calendar, which is especially created as a single common calendar to add and remove reservations.
The app has, so to speak, its own gmail address for this purpose. The app is accessed via the google API (gapi client).
In order for the users to be able to interact with the calendar, I have found out that I can only use the service account of the calendar / app.
So far I managed to:
create a service key for the service account,
share the calendar with the service account "dummy" user,
use that service key (certificate) to create a JWT token (using jsrsasign library),
make a POST request to get an access token for gapi,
initialise the gapi auth and client, and have access to the calendar via gapi
Now when I get to the point of retrieving the google Calendar events, I do get a successful response, but the array of events is empty, even though there are test events available in the shared calendar.
The response looks like this:
{
"kind": "calendar#events",
"etag": "\"pqef3g4h5j6j0g\"",
"summary": "my_app_email#appspot.gserviceaccount.com",
"updated": "2019-01-15T21:14:05.029Z",
"timeZone": "UTC",
"accessRole": "owner",
"defaultReminders": [],
"items": []
}
There are a few topics on Stackoverflow regarding this, but none of them have helpful information, or they are for Pythin / PHP.
I am hoping someone can give advice with this for Javascript...
I resolved this... The problem was in the gapi request, when fetching the events.
I was using the wrong calendarId. I had its value set to the default 'primary', but the actual calendarId to be used, can be found under the Google Calendar Settings >> Integrate Calendar. In my settings, the calendarId was the associated account's email address.
So the gapi request looks like this:
const fetchTimeLimit = new Date(2019, 0, 1).toISOString();
let events = await gapi.client.calendar.events.list({
calendarId: 'calendar_email#gmail.com',
timeMin: fetchTimeLimit,
showDeleted: false,
singleEvents: true,
maxResults: 300,
orderBy: 'startTime'
})
.then(response => { ........etc
With version 1 this is how I used to communicate with DialogFlow Api!
fetch(configs.baseUrl + "query?v=20150910", {
body: JSON.stringify({query: text, lang: "en", sessionId: "somerandomthing"}),
headers: {
'content-type': 'application/json',
"Authorization": "Bearer " + configs.accessToken,
},
method: 'POST',
})
.then(response => response.json())
.then(data => {
console.log(data.result.fulfillment.speech);
return data.result.fulfillment.speech;
})
.catch(error => console.error(error))
I simply had to pass the access token into header and that was it!
I dont know how can I make this code work with DialogFlow v2, I am getting stuck on the access token, one my V2 Agents I can not longer see access token but instead I have a Project Id and Service Account.
I manage to create Service key from google console and activate thru gcloud but I just dont know where to get or how to generate this access token, or do I need an access token into v2, if not, how do I deal with this?
A working example would much appreciated.
Note I have downloaded this file which contains these kind of data and used this file in gcloud and it said that service activated smth but then what?
is that all? what should I do next so I can make http call to V2 DialogFlow.
{
"type": "service_account",
"project_id": "xxxx",
"private_key_id": "xxxx",
"private_key": "-----BEGIN PRIVATE KEY-----xxxx",
"client_email": "xxxx",
"client_id": "xxxx",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://accounts.google.com/o/oauth2/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/dialogflow-client%40xxxx"
}
You did pretty much the right thing, but you would probably want to use Dialogflows Node.js client SDK. These SDKs read the authentication JSON file automatically when you instantiate a client (see the example on Github, the file is read by ... = new dialogflow.SessionsClient()).
Below is another example of creating your DialogFlow V2 access token using Node.js. The library that is used in the code below is google-oauth-jwt.
const googleAuth = require('google-oauth-jwt');
function generateAccessToken() {
return new Promise((resolve) => {
googleAuth.authenticate(
{
email: <client_email>,
key: <private_key>,
scopes: 'https://www.googleapis.com/auth/cloud-platform',
},
(err, token) => {
resolve(token);
},
);
});
}
You may find your client_email and private_key from the JSON key file you have downloaded from your Google Cloud Platform project's service account page. If you are unsure how/where to download it, you may checkout my blog post here.
To find out which scope which scope you may need, you may checkout the DialogFlow V2 REST API documentation page.
You might be interested in this:
https://cloud.google.com/docs/authentication/api-keys
I'm having issues getting Google cloud endpoints working in tandem with the YouTube data API v3 in my javascript client. I think my issue is around the gapi.client.setApiKey() method setting the key for both my endpoints API and the YouTube api. When I do set the key YouTube works but my endpoints do not and I see the following error using my endpoints API:
{
"domain": "usageLimits",
"reason": "accessNotConfigured",
"message": "Access Not Configured. The API () is not enabled for your project. Please use the Google
Developers Console to update your configuration.",
"extendedHelp": "https://console.developers.google.com"
}
Without the key my endpoints work but youtube search does not and I get this message using the search feature:
{
"domain": "usageLimits",
"reason": "dailyLimitExceededUnreg",
"message": "Daily Limit for Unauthenticated Use Exceeded. Continued use requires signup.",
"extendedHelp": "https://code.google.com/apis/console"
}
The code that loads the API is summarised below but essentially I have followed the endpoints python/javascript tutorial and the youtube data API tutorials!
init = function(apiRoot) {
var apisToLoad;
var callback = function(){
if(--apisToLoad == 0){
enableButtons();
}
}
apisToLoad = 2; // must match number of calls to gapi.client.load()
gapi.client.load('myAPIName', 'v1', callback, apiRoot);
gapi.client.load('youtube', 'v3', onYouTubeApiLoad);
};
// Called automatically when YouTube API interface is loaded (see line 9).
function onYouTubeApiLoad() {
//sets the api key
gapi.client.setApiKey('APIKeyForYouTubeFromDevConsole');
}
To verify only the youtube API requests with the API key remove the api.client.setApiKey method call.
In calls to the YouTube data API add a key parameter to the API request:
var request = gapi.client.youtube.search.list({
part: 'snippet',
type: 'video',
maxResults: 12,
q: searchValue,
key: 'YourAPIKeyGoesHere'
});
This means only these API calls are authorised and not the endpoints calls.
I'm not extremely familiar with the Youtube Data API. But I recognize the code you used for your Endpoints as the code that we provide. You can definitely use this code for the Endpoints API. For the Youtube Data one, I suggest looking here.
Looks like the code you need would be something like this :
import com.google.api.client.auth.oauth2.Credential;
import com.google.api.services.youtube.YouTube;
public class myClass {
/**
* Define a global instance of a Youtube object, which will be used
* to make YouTube Data API requests.
*/
private static YouTube youtube;
public static void main(String[] args) {
List<String> scopes = Lists.newArrayList("https://www.googleapis.com/auth/youtube");
try {
// Authorize the request.
Credential credential = Auth.authorize(scopes, "invideoprogramming");
// This object is used to make YouTube Data API requests.
youtube = new YouTube.Builder(Auth.HTTP_TRANSPORT, Auth.JSON_FACTORY, credential)
.setApplicationName([YOUR APP])
.build();
}
From there you should be able to use the youtube object to make your calls, and the gapi to send stuff to your endpoint.
I have trouble getting credentials in a browser script.
The authentication server returns cognito_identityId and cognito_token.
Then I set a Cookie:
$.cookie('cognito_identityId')
$.cookie('cognito_token')
I tried to get credentials in 4 ways on the browser, and all Failed:
CognitoIdentityCredentials
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'us-east-1:xxxxxxxxxxxx'
IdentityId: $.cookie('cognito_identityId'),
Logins: {
'myauth': $.cookie('cognito_token')
}
});
// => Error: Missing required key 'IdentityId' in params
assumeRoleWithWebIdentity
var params = {
RoleArn: 'arn:aws:iam::xxxxxxxxxxxx:role/Cognito_xxxxxxxAuth_Role',
RoleSessionName: 'xxxxxxxxxxx',
WebIdentityToken: $.cookie('cognito_token'),
DurationSeconds: 900,
ProviderId: 'myauth'
};
var sts = new AWS.STS({apiVersion: '2011-06-15'});
sts.assumeRoleWithWebIdentity(params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data); // successful response
});
// => AccessDenied: Not authorized to perform sts:AssumeRoleWithWebIdentity
PolicyDocument
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Federated": "cognito-identity.amazonaws.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"cognito-identity.amazonaws.com:aud": "us-east-1:xxxxxxxxxxxxx"
},
"ForAnyValue:StringLike": {
"cognito-identity.amazonaws.com:amr": "authenticated"
}
}
}
]
}
GetCredentialsForIdentity
var params = {
IdentityId: $.cookie('cognito_identityId'),
Logins: {
"myauth": $.cookie('oauth.io_token')
}
};
var cognitoidentity = new AWS.CognitoIdentity({apiVersion: '2014-06-30'});
cognitoidentity.getCredentialsForIdentity(params, function(err, data) {
if (err) {
console.log(err, err.stack); // an error occurred
}
else {
console.log(data); // successful response
}
});
// => InvalidParameterException: Please provide a valid public provider
WebIdentityCredentials
AWS.config.credentials = new AWS.WebIdentityCredentials({
RoleArn: 'arn:aws:iam::xxxxxxxx:role/Cognito_xxxxxxxxxxAuth_Role',
WebIdentityToken: $.cookie('cognito_token')
});
// => Error: There were 2 validation errors:
// * MissingRequiredParameter: Missing required key 'IdentityPoolId' in params
// * MissingRequiredParameter: Missing required key 'IdentityId' in params
Questions:
What am I doing wrong?
What is the correct way to use this?
Thank you.
Thank you for your kindness.
I tyied your advice, but did not change.
Error messages.
POST https://cognito-identity.us-east-1.amazonaws.com/ 400 (Bad Request)
POST https://cognito-identity.us-east-1.amazonaws.com/ 400 (Bad Request)
Error: Missing required key 'IdentityId' in params
at fail (chrome-extension://hmjdjbikinkmjbilihjibcihbkbjdgjf/bower_components/aws-sdk-js/dist/aws-sdk.js:2163:37)
at validateStructure (chrome-extension://hmjdjbikinkmjbilihjibcihbkbjdgjf/bower_components/aws-sdk-js/dist/aws-sdk.js:2084:14)
at validateMember (chrome-extension://hmjdjbikinkmjbilihjibcihbkbjdgjf/bower_components/aws-sdk-js/dist/aws-sdk.js:2110:21)
at validate (chrome-extension://hmjdjbikinkmjbilihjibcihbkbjdgjf/bower_components/aws-sdk-js/dist/aws-sdk.js:2059:10)
at Request.VALIDATE_PARAMETERS (chrome-extension://hmjdjbikinkmjbilihjibcihbkbjdgjf/bower_components/aws-sdk-js/dist/aws-sdk.js:800:32)
at Request.callListeners (chrome-extension://hmjdjbikinkmjbilihjibcihbkbjdgjf/bower_components/aws-sdk-js/dist/aws-sdk.js:3913:20)
at callNextListener (chrome-extension://hmjdjbikinkmjbilihjibcihbkbjdgjf/bower_components/aws-sdk-js/dist/aws-sdk.js:3903:12)
at chrome-extension://hmjdjbikinkmjbilihjibcihbkbjdgjf/bower_components/aws-sdk-js/dist/aws-sdk.js:787:9
at finish (chrome-extension://hmjdjbikinkmjbilihjibcihbkbjdgjf/bower_components/aws-sdk-js/dist/aws-sdk.js:126:7)
at chrome-extension://hmjdjbikinkmjbilihjibcihbkbjdgjf/bower_components/aws-sdk-js/dist/aws-sdk.js:142:9
There are source code below link.
https://github.com/bisque33/my-custom-dictionary
and server side is a AWS Lambda Function.
var aws = require('aws-sdk');
aws.config.region = 'us-east-1';
var cognitoidentity = new aws.CognitoIdentity();
var identityPoolId = 'us-east-1:0dccff0d-5fd7-4d14-b38f-d27204feaecc';
console.log('Loading function');
exports.handler = function(event, context) {
console.log('token: %s', event.token);
var params = {
IdentityPoolId: identityPoolId,
Logins: {
'oauth.io': event.token
}
};
cognitoidentity.getOpenIdTokenForDeveloperIdentity(params,function(err,data){
if(err){
console.log(err);
context.fail('Something went wrong');
}else{
context.succeed(data);
}
});
};
This program is Google-Chrome-Extension.
AWS Lambda Function returns token by getOpenIdTokenForDeveloperIdentity.
app/scripts/popup.js calls Lambda Function and set cookies.
app/scripts/background.js calls AWS.config.credentials.get, and returns error.
Am I using it wrong?
Update for Additional Information
Thank you for the additional information.
Error appears on 104 line on background.js
AWS.config.credentials.get(function(){
and 115 line on background.js
dataset.synchronize(
And, My explaination was not enough. Facebook authentication needs the domain(ex. http :// example.com). However, Google-Chrome-Ext does not have domain. It has a domain 'chrome-extension://xxxxxxxxxxxxxxxxxxxx'. Then, I use https://oauth.io. It proxies any authentication and accepts chrome-extension domain.
Popup.js does Facebook authentication through oauth.io sdk. It gets a facebook token, and gives to getOpenIdTokenForDeveloperIdentity. I think facebook token.substr(0,14) is unique. But, If it is wrong, I use another unique identifier(ex. email-address.)
Sorry, I was wrong. AWS.config.credentials.get gives an Error:
Error: Invalid login token.
And, dataset.synchronize shows this Error:
Error: Missing required key 'IdentityId' in params
The first approach you have, using CognitoIdentityCredentials, is most likely the best approach for you to take. I can't spot exactly what's causing the error for you but lets try a couple things:
When using Developer Authenticated Identities, you do need to specify the IdentityId when initializing CognitoIdentityCredentials. You need to get the IdentityId value from the call to GetOpenIdTokenForDeveloperIdentity. However, you shouldn't need to preserve the IdentityId value in a cookie as CognitoIdentityCredentials will cache the id by default in the browser's local storage.
As for your Logins map: It looks like you're trying to use Developer Authenticated Identities. With the JavaScript SDK, use the key 'cognito-identity.amazonaws.com' and make sure the value is the token returned from your backend's call to getOpenIdTokenForDeveloperIdentity.
If you continue to have problem using the CognitoIdentityCredentials approach, please reply here with some more info such as the exact method/code you're calling when you receive the error message, and the traced output (i.e. with console.log('%o',..)) of the params input just before your call to the CognitoIdentityCredentials constructor.
Update Based on Additional Information Provided
I still need to know exactly which line of code you receive the error on, but based on the information provided I think I can still help...
Based on what I see in background.js, it looks like you're trying to initialize CognitoIdentityCredentials using a Developer Authenticated Identities provider. This is where I'm guessing that you're receiving the error.
However, in Popup.js, it looks like you're trying to authenticate the user with Facebook. If you're authenticating your users with Facebook, you should just pass the facebook access token into your Logins map when using Cognito. Just use graph.facebook.com as the key in the Logins map and the access token from Facebook. More detail on how to do this is in the Facebook Integration topic of the Amazon Cognito developer guide.
Facebook vs Developer Authenticated Identities
We can get Developer Authenticated Identities to work for you, but in this case, it doesn't look like the right solution for you since you're not actually doing any additional authentication on the identity in your Lambda function and the unique user identifier that you're passing into the getOpenIdTokenForDeveloperIdentity operation appears to be the facebook token, which is not good by the way since the token itself will change between user sessions even for the same user. Usually a good unique identifier is an email address or a user id used by an internal system.
Facebook Login & Redirects
Since you're ultimately trying to use Facebook for login and Amazon Cognito has built-in integration for Facebook, the best thing for you to do is get an access token from Facebook and pass in the Facebook token to Cognito's login map directly. I'm not sure if this will work with Auth.io or not (I'm just not familiar with it), but as long as Auth.io gives your JavaScript code a bonefide facebook token and you add the same Facebook App ID to both Auth.io and Amazon Cognito's Console, it should work. However, you mentioned you want to use Auth.io to avoid Facebook doing a redirect to a landing page. I could be mistaken, but I'm pretty sure if you're using Facebook's JavaScript SDK you won't need a redirect page. You should only need the redirect page if you're doing Facebook's Manually Build a Login Flow.