​Hello All,
Wanted to do server-side integration for Gmail API. The basic need is using enabling Gmail API I want to read my Gmail inbox for some analytics purpose.
Golang Package - "google.golang.org/api/gmail/v1"
As per documentation, I have followed the below steps.
New Signed up with Gmail
Added billing details to use GCP services
Created test project
Enabled Gmail API
Created Service Account and key inside it. Got credentials.json file
On the backend side, I am using the Golang package to extract the
Gmail inbox.
After successful integration, I am trying to run my code but getting
the below error
{"level":"error","msg":"Error while creating gmail service : oauth2/google: no credentials found","time":"2021-07-25T15:11:23+05:30"}
Can anyone help me to figure out what is missing?
I currently use OAuth with YouTube and the device flow [1], so maybe this can be helpful. First you need to make a one time request like this:
data := url.Values{
"client_id": {"something.apps.googleusercontent.com"},
"scope": {"https://www.googleapis.com/auth/youtube"},
}
res, err := http.PostForm("https://oauth2.googleapis.com/device/code", data)
You'll get a response like this:
type OAuth struct {
Device_Code string
User_Code string
Verification_URL string
}
which you can do a one time prompt to user like this:
1. Go to
https://www.google.com/device
2. Enter this code
HNDN-ZWBL
3. Sign in to your Google Account
Then, after user log in, you can do an exchange request like this:
data := url.Values{
"client_id": {"something.apps.googleusercontent.com"},
"client_secret": {"super secret"},
"device_code": {"device code from above"},
"grant_type": {"urn:ietf:params:oauth:grant-type:device_code"},
}
res, err := http.PostForm("https://oauth2.googleapis.com/token", data)
This will give you an access_token and refresh_token, that you can save locally for reuse. In addition to "device flow", you also have "native app" flow [2], but I found the former to be a simpler process.
https://developers.google.com/identity/sign-in/devices
https://developers.google.com/identity/protocols/oauth2/native-app
Related
I'm writing a bot that takes the name of the music and the artist and adds it to the selected playlist with the zmb3 Spotify library. Everything works except the section add music to the playlist ;
the bot gives me this error: this request requires user authentication.
but Spotify token and Spotify secret are valid.
I searched, but I didn't get anything.
Is there anything more needed than Spotify Token and Spotify Secret?
What should I do?
Code :
// create spotify client connection and context to connect spotify
client, ctx := api.SpotifyConnection(SPOTIFYTOKEN, SPOTIFYSECRET)
// add playlist
playlist, err := client.GetPlaylist(ctx, PLAYLISTID)
if err != nil {
log.Fatal(err.Error())
}
/* SOME CODES */
if results.Tracks != nil {
items := results.Tracks.Tracks
musicID := items[0].ID.String()
cmd.AddMusic(client, ctx, playlist.ID.String(), musicID)
ERROR:
2021/12/26 11:06:25 This request requires user authentication.
Look at it this way - can you add a track to your playlist while not logged into your account? The same goes for your code. It's not an issue of authenticating to the API with TOKEN and SECRET_KEY, but you need to introduce user authentication, so that when a user opens your app, they log in and can add tracks to their own playlists.
There is an Authentication section in the zmb3 README that should help:
You can authenticate using a client credentials flow, but this does not provide any authorization to access a user's private data. For most use cases, you'll want to use the authorization code flow. This package includes an Authenticator type to handle the details for you.
Because Google AutoML does not have a golang client, I have to use the AutoML http client. To do so, requires an auth token from google which comes from running the following cli command:
gcloud auth application-default print-access-token
I am currently authing my Golang server with a credentials json file that has access to AutoML as well (example usage)
storageClient, err := storage.NewClient(ctx, option.WithCredentialsFile(gcloudCredsJSONPath))
My question is: how would I get an auth token from the Golang Google client if I have a JSON credentials file? Is this even possible?
Thank you for any help!
You can only use API tokens with certain Google Cloud APIs. Using tokens is discourage by Google Cloud as you can read in this article:
https://cloud.google.com/docs/authentication/
If your production environment is also Google Cloud, you might not need to use any JSON file at all. Google Cloud has the concept of "DefaultCredentials" that it injects in your services via the environment. You might be able to simplify your code to:
storageClient, err := storage.NewClient(ctx)
It's also recommended to use a "ServiceAccount" so the credentials that your application use can be scopes to it. You can read more here:
https://cloud.google.com/docs/authentication/getting-started
I have a problem refreshing an AWS Cognito token using server side authentication in Go. I am able to get the id_token, access_token and refresh_token with the cognitoidentityprovider.AdminInitiateAuth method. I have create a User Pools Client with secrets so I have to provide the SECRET_HASH in the AuthParameters.
This all works fine when logging in, but the same secret hash doesn't work when refreshing the tokens. I have tripple checked the code and verified the secret hashes I send are the same when logging in and when refreshing the token (it should be the same since it uses the username, clientID and clientSecret which don't change).
The AWS API returns the following error:
{
"error": "NotAuthorizedException: Unable to verify secret hash for client myClientIdHere\n\tstatus code: 400, request id: c186ecf2-57a7-11e8-a01e-f97ed64650c9"
}
I have checked that device tracking is off as the documentation mentions that this is a problem when refreshing tokens on the server-side (note under "Admin authentication flow", https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow.html#amazon-cognito-user-pools-server-side-authentication-flow).
My refresh code is:
AWSRefreshToken := aws.String(refreshToken)
secretHash := secretHash(email, auth.Config.ClientID, auth.Config.ClientSecret)
AWSUserPoolID := aws.String(auth.Config.UserPoolID)
input := cognitoidentityprovider.AdminInitiateAuthInput{
AuthFlow: aws.String("REFRESH_TOKEN_AUTH"),
AuthParameters: map[string]*string{
"REFRESH_TOKEN": AWSRefreshToken,
"SECRET_HASH": &secretHash,
},
ClientId: &auth.Config.ClientID,
UserPoolId: AWSUserPoolID,
}
output, err := auth.AWSCognitoIdentityProvider.AdminInitiateAuth(&input)
The secret hash code (from https://stackoverflow.com/a/46163403/3515197):
func secretHash(username, clientID, clientSecret string) string {
mac := hmac.New(sha256.New, []byte(clientSecret))
mac.Write([]byte(username + clientID))
return base64.StdEncoding.EncodeToString(mac.Sum(nil))
}
I have checked other Stack Overflow questions but they only mention the device tracking problem and that the secret hash is needed. What am I missing here?
Use user id (sub) instead of email while generating secret hash.
secretHash := secretHash(sub, auth.Config.ClientID, auth.Config.ClientSecret)
I got the answer for this after smashing my head for a few hours. You gotta use the SUB as the username when getting the refresh token during the REFRESH_TOKEN_AUTH/REFRESH_TOKEN flow. I do not see this documented anywhere, and it's not returned when logging in via email as username, so it's super confusing. Do this: After you log a user in, do a GetUser call serverside with the newly minted accessToken to get that user's USERNAME which will be the value of that user's SUB in the cognito dashboard... WHY IS IT NOT JUST THE EMAIL ADDRESS!? GREAT QUESTION. Store the username (aka: the sub) in the user's cookie, so when they need the refresh token, you can use the sub as the username here. sheesh... this was super confusing.
This is the case as well in Javascript. At work we have a Cognito User Pool with the Username attribute set to email, see Screenshot.
In my setup, some APIs can use the email to compute the Secret Hash but NOT initiateAuth(REFRESH_TOKEN_AUTH/REFRESH_TOKEN).
Here is a list of APIs that did work with the email as my username to compute:
initiateAuth(USER_PASSWORD_AUTH)
signUp
forgotPassword
confirmForgotPassword
confirmSignUp
resendConfirmationCode
We are having issues accessing the reseller api using service accounts.
The example with client secrets work well, but we would need to deploy this in k8s (Kubernetes Engine) without the need to refresh the oauth session on a recurring basis (especially doing this once, as it is kinda hard in a docker container).
While there is a lot of documentation on how to do this with python we could not find any way of getting access using a service account.
We tried two accounts, the default compute engine one and one created directly for our use case.
Both got the reseller scope in G Suite.
https://www.googleapis.com/auth/apps.order,
https://www.googleapis.com/auth/admin.directory.user,
https://www.googleapis.com/auth/siteverification,
We keep getting "googleapi: Error 403: Authenticated user is not authorized to perform this action., insufficientPermissions" errors though when using
client, err = google.DefaultClient(ctx, reseller.AppsOrderScope)
if err != nil {
log.Fatal("creating oauth client failed", zap.Error(err))
}
subs, err := client.Subscriptions.List().Do()
if err != nil {
log.Fatal("listing subscriptions failed", zap.Error(err))
}
I read on a post on StackOverflow, that the Reseller API requires user impersonation but searching on this throughout Google and the oauth2 and client lib repos did not result in a way to do this.
Python does this like described in the End-to-End Tutorial
credentials = ServiceAccountCredentials.from_json_keyfile_name(
JSON_PRIVATE_KEY_FILE,
OAUTH2_SCOPES).create_delegated(RESELLER_ADMIN_USER)
but for Go i could not find any documented way of doing this.
So few points here:
Reseller API only requires impersonation / domain-wide delegation when using a service account. In other words, the service account itself has no rights to call the API directly but it does have the ability to impersonate a reseller user (e.g. admin#reseller.example.com or such) who has rights to call the Reseller API.
You may be able to use regular 3-legged OAuth instead of a service account. You just need to make sure you request offline access so that you get a refresh token that is long lived.
Impersonation / domain-wide delegation is not compatible with the default service accounts built-in to AppEngine and ComputeEngine. You must use a service account you created in your API project.
See if the samples Google provides get you where you need to be.
The problem was resolved by using a different config and then setting the jwt.Subject which apparently does impersonation:
const envVar = "GOOGLE_APPLICATION_CREDENTIALS"
if filename := os.Getenv(envVar); filename != "" {
serviceAccount, err := ioutil.ReadFile(filename)
if err != nil {
log.Fatal("creating oauth client failed", zap.Error(err))
}
config, err := google.JWTConfigFromJSON(serviceAccount,
reseller.AppsOrderScope,
)
config.Subject = *impersonationUser // like user#google.com
client = config.Client(ctx)
}
Trying to create an application that updates links in my domains emails. I have got the service account element working, and can retrieve all the email accounts in my domain on Google Apps.
So with
config, err := google.JWTConfigFromJSON(data,
"https://www.googleapis.com/auth/admin.directory.user",
"https://www.googleapis.com/auth/gmail.readonly")
I create a new admin object, and srv.Users.List() giving me the users.
I now want to start a watch on each account so that Gmail can push emails to my app.
To do this, I need an access token and the impersonate the email address of the account I want to start the watch on.
ctx := context.Background()
tokenSource := config.TokenSource(ctx)
token, err := tokenSource.Token()
where config comes from JWTConfigFromJSON earlier.
It attemps to make the watch request, but I get the error googleapi: Error 403: Insufficient Permission, insufficientPermissions
First issue: I'm not sure I can get the token like this, do I have to have a seperate oAuth config and then use that to get the token? (That would surely require user interaction).
Second issue: I suspect I have not set the correct permissions, in either the code, or in the Service Account, but I'm not sure what they should be
Last issue: in the JWTConfigFromJSON call, When I remove the gmail scope, it it successfully gets a token. When I put it back I get "Unauthorized client or scope in request."
Any help greatly appreciated!
Thanks