msgraph-sdk-go example code for getting user's drive fails - go

The example code for the msgraph-sdk-go to get a user's drive fails with a 401 error below. It expects the request body to contain a client_secret, although there is no place in the example code to create a request body.
The example code does successfully authenticate to my registered application via a web browser.
What is required to use the msgraph-sdk-go?
Here's the code that fails:
result, err := client.Me().Drive().Get(context.Background(), nil)
if err != nil {
fmt.Printf("Error getting the drive: %v\n", err)
printOdataError(err)
}
fmt.Printf("Found Drive : %v\n", *result.GetId())
Here's the error:
Error getting the drive: DeviceCodeCredential authentication failed
POST https://login.microsoftonline.com/efa4b4f3-5e38-4866-9206-79c604d86e7c/oauth2/v2.0/token
--------------------------------------------------------------------------------
RESPONSE 401 Unauthorized
--------------------------------------------------------------------------------
{
"error": "invalid_client",
"error_description": "AADSTS7000218: The request body must contain the following parameter: 'client_assertion' or 'client_secret'.\r\nTrace ID: b6f28bb4-6bed-4dfe-a275-c0343fb91e01\r\nCorrelation ID: c06d2257-b3ab-4df3-ba58-ab271cf97508\r\nTimestamp: 2023-02-14 14:18:22Z",
"error_codes": [
7000218
],
"timestamp": "2023-02-14 14:18:22Z",
"trace_id": "b6f28bb4-6bed-4dfe-a275-c0343fb91e01",
"correlation_id": "c06d2257-b3ab-4df3-ba58-ab271cf97508",
"error_uri": "https://login.microsoftonline.com/error?code=7000218"
}
Edit: Adding more detail for the answer from baywet
I have the client and device code credentials created as baywet shows but I added the TennantID field.
In point 5 I selected mobile/desktop application but with redirect URI of http://localhost. I have different options for Redirect URIs than he has.
Point 6 was the key to getting it to work. Once I selected Yes for Enable the following mobile and desktop flows my application worked.
I also had these API permissions.

Assuming you setup your client using the device code credentials given the error message you're getting with code similar to this
cred, err := azidentity.NewDeviceCodeCredential(&azidentity.DeviceCodeCredentialOptions{
ClientID: "CLIENT_ID",
UserPrompt: func(ctx context.Context, message azidentity.DeviceCodeMessage) error {
fmt.Println(message.Message)
return nil
},
})
client := msgraphsdk.NewGraphServiceClientWithCredentials(cred, []string{"User.Read"})
The registered application needs to be configured properly to allow for the device close flow.
For that:
Go to the azure portal
Navigate to the application registrations (Azure Active Directory, then Application registrations).
Find your application registration in the list.
Click on the authentication tab
Make sure the mobile and desktop applications platform is select with the https://login.microsoftonline.com/common/oauth2/nativeclient URL checked.
Make sur "Enable the following mobile and desktop flows" is set to "yes".
Click "save".
A couple of screenshots to guide you through.

Related

InteractiveBrowserCredentiall authentication failed POST https://login.microsoftonline.com/oauth2/v2.0/token in golang AD implementation

The app is authorizing but not getting the token. Getting error
InteractiveBrowserCredentiall authentication failed
Response: Tokens issued for the 'Single-Page Application' client-type may only be redeemed via cross-origin requests.
package main
import (
"context"
"fmt"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
azi "github.com/Azure/azure-sdk-for-go/sdk/azidentity"
)
func main() {
cred, err := azi.NewInteractiveBrowserCredential(&azi.InteractiveBrowserCredentialOptions{
TenantID: "<Tenant-id>",
ClientID: "<Client id>",
RedirectURL: "http://localhost:3000/auth/",
})
if err != nil {
fmt.Println(err.Error())
return
}
fmt.Println("No error 😎")
var ctx = context.Background()
policy := policy.TokenRequestOptions{Scopes: []string{"User.Read"}}
fmt.Println(cred.GetToken(ctx, policy))
}
On the Authentication blade, please check if it is set to the
Single-Page Application platform and change to web app or mobile &
desktop applications depending on the requirement of the app.
As when authenticating with Single Page Applications (SPA), the
implicit grant flow (or hybrid flow) method of authenticating is not
the most secure of scenarios as it relies solely on browser
redirects and the access token are returned as part of the query.
This error usually occurs because the /token endpoint expects a browser to make request and when the actual WebApp is using back-channels to retrieve the token securely via a HttpClient or vice versa
Basically it checks if the Origin: header is sent or not with the request which browsers do.
So if you want to mitigate the error when using other platforms than
SPA , try to add some value to Origin header something like
here in the code.
If you have a Web Application, make sure that the 'Origin" header is
not sent to the server when exchanging a code for a token.
References:
cross-origin
token redemption is permitted only for the 'Single-Page Application'
client type. - Microsoft Q&A
'Single-Page Application' client-type may only be
redeemed via cross-origin requests - Stack Overflow

Golang - spotify : This request requires user authentication

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.

Gmail API - Oauth2/google: no credentials found (Golang)

​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

Drive API App OAuth Permissions Not Updating

I am working on a Golang service that will connect to Google Drive and download a file. The issue is that no matter how I update the projects permissions, when I run application and go to the browser window to OAuth, it only shows the metadata permissions needing granted when have granted more so that I can download files.
Here are the steps I've taken for the service:
I have created a project in Google Developer Console.
In that project I have enabled and updated Google Drive permissions to the capture all auth/drive permission (I have also tried granting all and only some granular).
I create the Golang service from this example: https://developers.google.com/drive/api/v3/quickstart/go
I run the Golang app which prompts me to open a URL in browser to OAuth authenticate.
I open link in browser, auth with my app-owner gmail account and every single time no matter what I do this is the only OAuth scope that shows:
If I use the Drive API Explorer to perform the file download it shows that I need these permissions:
When i click "Execute" in the explorer the OAuth popup shows all the permissions I would expect for my app and works correctly:
What am I doing wrong or missing?
Could this be because the app is an "internal" app?
The issue was in the Golang Quick-Start boilerplate code that I used to scaffold the application.
This line was overriding the permission settings I was explicitly setting in console.
// If modifying these scopes, delete your previously saved token.json.
config, err := google.ConfigFromJSON(b, drive.DriveMetadataReadonlyScope)
if err != nil {
log.Fatalf("Unable to parse client secret file to config: %v", err)
}
fixed when changed to:
// If modifying these scopes, delete your previously saved token.json.
config, err := google.ConfigFromJSON(b, drive.DriveScope)
if err != nil {
log.Fatalf("Unable to parse client secret file to config: %v", err)
}

Unable to access Google Cloud Storage from Google Kubernetes Engine using default service account and google cloud libraries

I wrote an application that has a function to upload image through Google Kubernetes Engine using go lang. Everything else works fine, but I kept running into problems when I try to write image to Google Cloud Storage.
Here is my code in golang that actually uses the google storage api:
func putImage(imageURL string, image multipart.File) bool {
fmt.Println("Putting into image location : " + imageURL)
contextBackground := context.Background()
storageClient, err := storage.NewClient(contextBackground)
if err != nil {
fmt.Println("No client.")
return false
}
bucket := storageClient.Bucket("mytestbucketname")
bucketWriter := bucket.Object(imageURL).NewWriter(contextBackground)
bucketWriter.ContentType = "image/jpg"
bucketWriter.CacheControl = "public, max-age=0"
size, err := io.Copy(bucketWriter, image)
if err != nil {
fmt.Println("failed to put image")
return false
}
fmt.Println(size)
fmt.Println("Successfully put image")
bucketWriter.Close()
return true
}
The above function always returns true and size is always greater than 0. However, when I check the bucket, it does not actually have anything inside. So I researched around and realized that the default service account only has a read permission to Cloud Storage. Which is very weird because it should be returning false or size should be 0 or even outputting an error of permission denied.
The official cloud.google.com/go/storage API says Bucket() function returns a BucketHandle that does not actually perform any network operations, which makes sense as to why I was not getting permission denied error (?). And so I decided to check if I am actually getting anything from the Client or Bucket, just to see if the read permission is working. I added the following code :
attr, err := bucket.Attrs(contextBackground)
if err != nil {
fmt.Println(err.Error())
}
fmt.Println("bucket name : " + attr.Name)
And at this point I started to get fatal errors that shuts down my application. The error I got when trying to retrieve bucket's attributes is this :
Get https://www.googleapis.com/storage/v1/b/mytestbucketname?alt=json&prettyPrint=false&projection=full: oauth2: cannot fetch token: Post https://oauth2.googleapis.com/token: x509: certificate signed by unknown authority
So I thought I need to add ca certificate to my image. But it just does not sound logical to me because if I was running my image in Google Kubernetes Engine and it was accessing my Google Cloud Storage, given that it already has a default service account with read permission, why would I need a certificate? I made a new cluster with version 1.12.8-gke.10 and made sure to disable issue client certificate and made sure I had read permission to Storage and still got the same error. And I added this line to my docker file and still got the same error:
RUN apk --no-cache add ca-certificates && update-ca-certificates
I've been at it for two days and now I'm running out of ideas. My question is what am I doing wrong that keeps giving me "x.509 certificate signed by unknown authority" error when trying to access the Storage bucket attributes from Kubernetes Engine when I am using default permission? Technically getting bucket attributes is just a read function, which I am supposed to be able to do with default permission right? If anyone has any ideas or has ever ran into the same problem, please give me some help! Thanks!
If you are using the default cluster service account, you need to make sure the cluster has sufficient scopes to write to storage. By default, GKE clusters only have the read scope which blocks write attempts to GCS buckets.
gke-default:
https://www.googleapis.com/auth/devstorage.read_only
https://www.googleapis.com/auth/logging.write
https://www.googleapis.com/auth/monitoring
https://www.googleapis.com/auth/service.management.readonly
https://www.googleapis.com/auth/servicecontrol
https://www.googleapis.com/auth/trace.append
Without the write scope enabled, regardless of the credentials, you won't be able to write to buckets.
Note: You can use the access token only for scopes that you specified when you created the instance. For example, if the instance has been granted only the https://www.googleapis.com/auth/storage-full scope for Google Cloud Storage, then it can't use the access token to make a request to BigQuery.
If you want to bypass scopes, create a custom service account for the application, download the JWT token and mount these credentials directly into your code. This is the recommended method to authenticate your application against Google APIs
So I finally figured it out, just leaving this here if anyone ever runs into this problem too. It was my mistake of being very careless, but after reading this question :
Cannot exchange AccessToken from Google API inside Docker container
I was able to narrow it down to a certificate problem. It was actually because I did not install the ca-certificate properly. Because I was using multi stage build in my docker file, I misplaced this line of code :
RUN apk --no-cache add ca-certificates && update-ca-certificates
After placing it correctly it worked!

Resources