I'm trying to use the Google Auth Python library to perform OAuth2 into my own YouTube account, so I can gather metrics of my own subscriptions. The code, however, isn't working.
I have setup a service account, and that process created a "Compatible OAuth2" client, and I exported the JSON key file from that. I have also setup an API key, which is also enabled to do all the things (yes, I know, the sec-eng soul inside you is dying) ...
Here is the code:
# Python 3.10.0
from google.oauth2 import service_account
import requests
import json
import os
# Start an OAuth session
service_account_info = json.load(open(f'{os.path.dirname(__file__)}/.config/service_account.json'))
credentials = service_account.Credentials.from_service_account_info(service_account_info)
# API Key
with open(f'{os.path.dirname(__file__)}/.config/.apikey') as f:
API_KEY = f.read()
HEADERS = {'Accept': 'application/json', 'Content-Type': 'application/json', 'Authorization': f'Bearer {credentials}'}
# Construct the URL
URL = 'https://www.googleapis.com/youtube/v3/subscriptions'
# Parameters
PARAMS = {'part':'id', 'maxResults':'250', 'order':'alphabetical', 'mine':'true', 'key': API_KEY}
# Make the request
request = requests.get(URL, headers=HEADERS, params=PARAMS)
response = request.json()
# Print the response
print(json.dumps(response, indent=4))
But I'm getting this error:
{
"error": {
"code": 401,
"message": "Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",
"errors": [
{
"message": "Invalid Credentials",
"domain": "global",
"reason": "authError",
"location": "Authorization",
"locationType": "header"
}
],
"status": "UNAUTHENTICATED"
}
}
I'm fairly confident that the problem is in how I'm handling the credentials but I don't know how that is supposed to go.
I appreciate your input and help.
All I'm trying to do is list my own YouTube Channel Subscriptions.
Thanks!
The YouTube data api does not support service account authentication to standard YouTube accounts. hense the OAuth 2 access token in the error message.
Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential.
rumor
I have heard that it does work with google workspace domain YouTube accounts because then you can configure the domain wide delegation. I have not tried
Other options
You can use standard oauth2 and authorize your application once. Store the refresh token and then your application can then just use the refresh token to request a new access token as needed. Just remember to set your application in production or your refresh token will expire after seven days and you will need to authorize the application again.
Related
I am working on a project to let a client authorize their google ads account, and then use those authorized credentials to download data on their behalf. I have a webapp that successfully Authorizes the app to do things on the clients behalf. This generates an access code that I then trade for two credentials, an access token and a refresh token. This refresh token then gets passed to a database, where a separate app attempts to query the googleAds API.
It is my understanding that the Google Oauth engine only needs the refresh token.
I am trying to authorize by use of load_from_dict() or load_from_env() methods of the GoogleAdsClient class. Both yield the same error: google.auth.exceptions.RefreshError: ('invalid_client: Unauthorized', {'error': 'invalid_client', 'error_description': 'Unauthorized'})
I have verified the developer_token, client_id, and client_secret are all accurate to what is in the API console. I have also verified the refresh_token is being passed correctly to the credential dict.
I am really at a loss on where to go from here. I have read many stack overflow threads with similar titles, and yet I am still stuck at the same place.
Here are some relevant links.
Google Ads API configuration
Google Identity and Server side web apps
Google's example of an API call
Relevant code
class GoogleAds:
def __init__(self):
self.scope = ['https://www.googleapis.com/auth/adwords']
self.client_id = os.getenv('GOOGLE_ADS_CLIENT_ID')
self.client_secret = os.getenv('GOOGLE_ADS_CLIENT_SECRET')
self.developer_token = os.getenv('GOOGLE_ADS_DEVELOPER_TOKEN')
self.refresh_token = os.getenv('GOOGLE_ADS_REFRESH_TOKEN')
def authorize(self):
credentials = {
"developer_token": self.developer_token,
"refresh_token": self.refresh_token,
"client_id": self.client_id,
"client_secret": self.client_secret,
"use_proto_plus":"True",
"grant_type": "refresh_token",
}
print(credentials)
googleads_client = GoogleAdsClient.load_from_dict(credentials)
service = googleads_client.get_service("GoogleAdsService")
request = googleads_client.get_type("SearchGoogleAdsRequest")
return service, request
'error': 'invalid_client', 'error_description': 'Unauthorized' Can be a very hard error to debug. It can mean a number of things.
Currently it Implies to me that the user has not authorized this client.
First ensure that the refresh token has not expired. Second ensure that the client id and client secrete used to create the refresh token are the same one that you are using to request a new access token.
oauth2#expiration
I ended up refreshing the Client_Secret in the google API client and that seemed to have gotten me through.
Q: It is outside the scope of this question, but is it possible to get that value from the authorization step?
A: You can get the customer IDs you have access to with the client.get_service("CustomerService") method. There is also a way to get account hierarchy. I will probably be using (Frankensteining) that to move forward
I try to get a user's custom attribute using a graph api call :
I have setup an app registration with User.ReadWrite.All Delegated privileges as stated in the documentation and Granted admin privileges:
Using python, I can fetch a token using my app registration secret :
def retrieve_token(endpoint, client_id, client_secret):
# endpoint = "https://login.microsoftonline.com/<tenant-id>/oauth2/token"
payload = {
"grant_type":"client_credentials",
"client_id": client_id,
"client_secret": client_secret,
"resource":"https://graph.microsoft.com"
}
r = requests.request("POST", endpoint,data=payload).json()["access_token"]
return r # returns a valid jwt token
Using that token I then call a function to get my user's attributes :
def get_user_role(endpoint,user_uid, token):
# endpoint = "https://graph.microsoft.com/v1.0/users/"
url = endpoint + user_uid
headers = {
"Authorization": "Bearer " + token,
"Content-Type": "application/json"
}
return requests.request("GET", url, headers=headers).json()
For some reason, it lacks of privileges :
{'error': {'code': 'Authorization_RequestDenied', 'message': 'Insufficient privileges to complete the operation.', 'innerError': {'date': '2022-03-29T16:11:38', 'request-id': 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', 'client-request-id': 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx'}}}
If I go back to my api's permissions and change the User.ReadWrite.All from Delegated permissions to Application permissions it works.
From what I read in this scenario I should use Delegated permission but how to get that to work?
If you are using the client credentials follow(Authenticating using Client ID and Client Secret) so you should require to Application permissions to get token and call a function to get my user's attributes. So it won't Possible to calling an API with Client Crendetial Token using delegated permissions.
As junnas stated in comment is correct You typically use delegated permissions when you want to call the Web API as the logged on user.
Application Permissions: Your application needs to access the web API directly as itself (no user context).
So you should change the way to get the access token use e.g. authorization code flow or device code flow to get an access token as a signed in user
You can refer this Document can help Desktop app that calls web APIs: Acquire a token using Username and Password
I'm trying to fetch a report from Amazon advertising using python script and I can't find a way to get the authorization code. All amazon documentation refer to websites and none for automatic scripts.
Any idea how can I implement it?
Thanks.
What you need:
a registered application on developers.amazon.com
client_id and client_secret for this app
consent to access an Amazon Advertising Account (this is done via 'Login with Amazon')
As a result of the consent process you get a refresh token.
With the client_id, the client_secret and the refresh token you can build a python script to receive an access token - and with the access token you can access the Amazon Advertising API.
Resources:
https://advertising.amazon.com/API/docs/en-us/setting-up/account-setup
https://advertising.amazon.com/API/docs/en-us/get-started/overview
EDIT:
I recommend to use a tool like Postman or SoapUI for easy testing of auth and api requests...
I had the same problem and using tector's comment I built a simple function that uses the Refresh Token (which never expires) to generate the Authorization Code, which is valid for 60 minutes:
import requests
import json
host = 'https://api.amazon.com/auth/o2/token'
body = {
"grant_type": "refresh_token",
'client_id': YOUR CLIENT ID,
'refresh_token': YOUR REFRESH TOKEN,
'client_secret': YOUR CLIENT SECRET
}
headers = {"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8"}
def authenticator():
r = requests.post(host, body,
headers=headers)
r.raise_for_status()
r = r.json()
with open('FILENAME.json', 'w') as f:
json.dump(r, f)
I'm trying to use the Microsoft Graph API to query an Outlook/O365 mailbox for messages. I registered my app in the Azure portal and received the necessary information to query the API. The app has the Mail.Read permission. (I don't have access to the Azure portal, I was told it was set up this way.) When I get my token from the OAuth endpoint, however, it doesn't work in any subsequent calls. I'm using Python's requests module for testing right now.
Why is this call failing? It seems like I'm passing all of the correct information but I'm clearly missing something.
I'm getting the token by performing a POST on:
https://login.microsoftonline.com/my.domain/oauth2/token
I pass the necessary parameters:
data = {'grant_type': 'client_credentials', 'client_id': CLIENTID, 'client_secret': SECRET, 'resource': APPURI}
and I get a response like this:
{
'resource': 'APPURI',
'expires_in': '3599',
'ext_expires_in': '3600',
'access_token': 'TOKENHERE',
'expires_on': '1466179206',
'not_before': '1466175306',
'token_type': 'Bearer'
}
I try to use that token, however, and it doesn't work for anything I call. I'm passing it as a header:
h = {'Authorization': 'Bearer ' + TOKEN}
I'm calling this URL:
url = 'https://graph.microsoft.com/v1.0/users/my.email.address#example.com/messages'
Specifically, I use this:
r = requests.get(url, headers=h)
The response is a 401:
{
'error': {
'innerError': {
'date': '2016-06-17T15:06:30',
'request-id': '[I assume this should be removed for privacy]'
},
'code': 'InvalidAuthenticationToken',
'message': 'Access token validation failure.'
}
}
in your login request, the resource parameter should be https://graph.microsoft.com
It seems to be the case, that tokens issued from the v1 endpoint aren't valid for atleast some requests with MS Graph API.
Instead try to get the token form the v2 endpoint by calling https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token.
In case you are working with oidc discovery documents, you'll find the one for v2 at https://login.microsoftonline.com/{tenant}/v2.0/.well-known/openid-configuration
I think you will need to register app from here "https://apps.dev.microsoft.com" instead of from Azure Portal.
Unless you are an using Client Credentials, you cannot access the messages another account's mailbox. Make sure that my.email.address#example.com is the same account you are authenticated with and that this address is also the userPrincipalName for the account.
You can also use a simplified URI for requesting your messages and bypassing determining the account's userPrincipalName by using /me. In this case the GET request would be https://graph.microsoft.com/v1.0/me/messages
It's worth noting that even if MS's Azure documentation does not specify the need for listing the resource, I could never get to work without listing the resource.
https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-v2-protocols-oauth-client-creds.
There is a supplementary document specifiy to two-legged Auth for MS Graph that actually uses the 'resource' in the example.
https://developer.microsoft.com/en-us/graph/docs/authorization/app_only
Happy hunting!
I'm attempting to use Instagram's location search endpoint documented here: https://www.instagram.com/developer/endpoints/locations/
I've registered my app and gotten a client ID.
When I intentionally omit a client ID or access token and submit the following:
https://api.instagram.com/v1/media/search?lat=48.858844&lng=2.294351
I get:
{
"meta": {
"error_type": "OAuthParameterException",
"code": 400,
"error_message": "Missing client_id or access_token URL parameter."
}
}
This makes sense, and tells me I should be able to use a client_id for this call (meaning users won't have to authenticate to Instagram).
However, when I append &client_id=VALID_CLIENT_ID
I get:
{
"meta": {
"error_type": "OAuthAccessTokenException",
"code": 400,
"error_message": "The access_token provided is invalid."
}
}
Am I correct that I should be able to use this call without authenticating? If so, any thoughts on what I'm doing wrong?
Thanks!
According to the instagram OAuth 2.0 docs, you need an access_token in order to make authenticated requests.
In order to get the access token, your service needs to redirect the user to instagram to have them log in. From here, instagram will redirect the user to a url of your choice once the user has granted you access with the access_token in the url string.
The Instagram OAuth 2.0 docs has more details.
Also, I guess I misread the question. Here's documentation from Instagram saying that client_id is no longer supported for endpoints:
The Instagram API requires an access_token from authenticated users
for each endpoint. We no longer support making requests using just the
client_id.
Your request is for /media/search endpoint which does require access_token (and so does /location/search, btw.).