How to authorize via token in Python to an API - python

I´m completly new in Python-programming and I´m trying to get access to a e – scooter rental service and crawl the data oft he scooters. The service provider is Bird and has no official API and is only accessible via an app for Android or iOS. Someone found a way and the requirements for the posts and gets are here documented: https://github.com/ubahnverleih/WoBike/blob/master/Bird.md
My problem is after i authorized myself by the token I can´t create a get-request. The Server says all the time:
{"code":401,"message":"Credentials are required to access this resource"}
This is my code:
import requests
import json
import uuid
target = "https://api-auth.prod.birdapp.com/api/v1/auth/email"
headers = { "User-Agent": "Bird/4.53.0 (co.bird.Ride; build:24; iOS 12.4.1) Alamofire/4.53.0"
,"Platform": "ios",
"App-Version": "4.53.0",
"Content-Type": "application/json",
"Device-Id": str(uuid.uuid4())}
data = {"email": "any#mail.com"}
reply = requests.post(target, data=json.dumps(data), headers=headers)
# at this point u get an email and u need to copy the token in the email
token = {"token":"IGzMtdAkQ3icmSFV0V64yQ"}
url_token = 'https://api-auth.prod.birdapp.com/api/v1/auth/magic-link/use'
# now you are authorized and you can start get-requests
get_headers = {"Authorization" : "IGzMtdAkQ3icmSFV0V64yQ",
"Device-id" : str(uuid.uuid4()),
"App-Version" : "4.41.0",
"Location" : json.dumps({"latitude":37.77249,"longitude":-122.40910,"altitude":500,"accuracy":100,"speed":-1,"heading":-1})}
url_get_birds = "https://api.birdapp.com/bird/nearby?latitude=37.77184&longitude=-122.40910&radius=1000"
print((requests.get(url_get_birds, headers = get_headers)).text)
The last print command gives me the 401 error all the time
I guess i don´t use the token in get_headers as an authorization correct…
Sorry to bother you but I really don´t know how to progress.
Thank you very much

I don't think you're using the correct token. It appears to me that you are using the Magic Link token as your Auth for getting scooter locations. The Magic Link token that is included in your Email can't be used as the token to get locations of scooters, it's used as the token to get hold of your Access Token. The correct token should start with something like ey and a long list of characters.
Try run this to get your actual token:
curl --location --request POST 'https://api-auth.prod.birdapp.com/api/v1/auth/magic-link/use' \
--header 'User-Agent: Bird/4.53.0 (co.bird.Ride; build:24; iOS 12.4.1) Alamofire/4.53.0' \
--header 'Device-Id: <YOUR-UUID>' \
--header 'Platform: ios' \
--header 'App-Version: 4.53.0' \
--header 'Content-Type: application/json' \
--header 'Content-Type: text/plain' \
--data-raw '{"token":"9BntNB548R7KyD42ml4hrJA"}'
This will output 2 kinds of tokens - Access and Refresh. The Access Token is the one that you would use to get locations of scooters. It should be implemented like this: Authorization: Bird <ACCESS-TOKEN>

Related

Python OAuth Client Credentials Grant Type Returned 400, [error: unsupported grant] type but Curl Works Fine

I am very new to APIs (still learning) and I encountered a very weird issue with Python requests library when trying to initiate an OAuth Authentication flow with Client Credentials Grant Type.
For some reason, whenever I used my Python script (with the help of requests library) to send the HTTP request to the authentication endpoint, I always get
Response Status Code: 400
Response Body/Data returned: {"error":"unsupported_grant_type"}
However, if I tried using curl command line tool to send the request, I will get a successful response with status code 200 with the access token in the response body like this:
{'access_token': 'some access token',
'expires_in': 'num_of_seconds',
'token_type': 'Bearer'}
As a matter of fact, if I tried sending the request using Curl command line tool WITHIN my Python Script (with subprocess.Popen function), I can get the response with status code 200 and the access token with no problem.
Now, with that said, here's the Python script that I used to send the request to initiate the OAuth authentication flow:
import requests
import os
import base64
clientCredentialEndpoint = "https://base_url/path/token"
client_id = os.environ.get('CLIENT_ID')
client_secret = os.environ.get('CLIENT_SECRET')
# -- Encode the <client_id:client_secret> string to base64 --
auth_value = f'{client_id}:{client_secret}'
auth_value_bytes = auth_value.encode('ascii')
auth_value_b64 = base64.b64encode(auth_value_bytes).decode('ascii')
queryParams ={
'grant_type':'client_credentials',
'scope':'get_listings_data'
}
headers = {
'Authorization':f'Basic {auth_value_b64}',
'Content-Type':'application/x-www-form-urlencoded'
}
# send the post request to Authorisation server
response = requests.post(
clientCredentialEndpoint,
params=queryParams,
headers=headers,
)
print(response.status_code)
print(response.text)
whereas the curl command that I used (and worked) to send the request is:
curl -X POST -u '<client_id>:<client_secret>' \
-H "Content-Type: application/x-www-form-urlencoded" \
-d 'grant_type=client_credentials&scope=get_listings_data' \
'https://base_url/path/token'
Again, like I said, if I execute this curl command inside a Python script, it will successfully return the access token with no issue.
Does anyone know what I did wrong in my Python script which caused my request to always fail?
Thanks in advance!
My goodness me, I just realised that the -d in the curl command does not correspond to query params, it stands for 'data'.
Hence, I just need to change my Python script requests.post() a bit so that it looks like this:
response = requests.post(
clientCredentialEndpoint,
data=queryParams,
headers=headers,
)
Hope this helps others.

How to pass a list to data parameter in requests.patch?

I'm trying to add a role to a user in Auth0 via requests in python, but I'm having problems with the data part. I've tried to send this data passing a pure list, using json parameter instead of data and other options...
requests.patch(
url='my_url',
data=json.dumps(["my_role_id"]),
headers={'Authorization': 'Bearer my_token',
'Content-Type': 'application/json'}
)
Following the docs, if I try to send the same data via cURL or in a client like Insomnia, it works.
curl --request PATCH \
--url 'https://{extension_url}/users/{user_id}/roles' \
--header 'Authorization: Bearer {access_token}' \
--header 'Content-Type: application/json' \
--data '[ "{role_id}" ]'
The data content passed to service in the python sample is not the same as in the cURL sample. Try this (postman-echo helps to figure out what is going on):
import requests
import json
role_ids = ["my-role-id"]
role_id_array = [ "{%s}" % x for x in role_ids ]
resp = requests.patch(
url='http://postman-echo.com/patch',
data=json.dumps(role_id_array),
headers={'Authorization': 'Bearer my_token',
'Content-Type': 'application/json'}
)
print(resp.content)
I solved it! My url was wrong with the user_id incomplete and Auth0's authorization API didn't tell me that.

Why do I always get 'The server has rejected the client credentials.' when I call business central api?

Here is my MSAL authentication:
#app.route('/get-microsoft-data', methods=('GET', 'POST'))
def get_microsoft_token():
public_app = ConfidentialClientApplication(
client_id="<client_id>", authority="https://login.microsoftonline.com/<tenant_id>",
client_credential="<client_secret>"
)
result = None
result = public_app.acquire_token_silent(["https://api.businesscentral.dynamics.com/.default"], account=None)
if not result:
print("No suitable token exists in cache. Let's get a new one from AAD.")
result = public_app.acquire_token_for_client(scopes=["https://api.businesscentral.dynamics.com/.default"])
if "access_token" in result:
global microsoft_token
microsoft_token = result["access_token"]
return redirect('/')
This is my call to business central api:
#app.route('/send-data-to-microsoft', methods=('GET', 'POST'))
def send_data_to_microsoft():
print(microsoft_token)
headers = {
"Authorization": "Bearer " + microsoft_token
}
r = requests.get("https://api.businesscentral.dynamics.com/v1.0/<tenant_domain>/sandbox/api/v1.0/companies", headers=headers)
print(r.json())
return redirect('/')
And this is the error I'm getting when I call /send-data-to-microsoft:
{'error': {'code': 'Authentication_InvalidCredentials', 'message': 'The server has rejected the client credentials. CorrelationId: ff4d9d32-db03-4c2a-bf77-2e6186d4988c.'}}
This is the doc for what I want: https://learn.microsoft.com/en-us/dynamics-nav/api-reference/v1.0/api/dynamics_companies_get
This is the list of valid endpoints for business central: https://learn.microsoft.com/en-us/dynamics-nav/api-reference/v1.0/endpoints-apis-for-dynamics
Client credential flow is NOT supported here. The supported Authentication methods for Dynamics 365 BC are only these 2 options based on the official document:
Basis Authentication
AAD authentication
If you want to call D365 BC API with a method which doesn't require user interaction, you should choose Basis Authentication.
The steps to do it are:
To set up basic authentication, log into your tenant, and in the
Search field, enter Users and then select the relevant link.
Select the user to add access for, and on the User Card page, in the
Web Service Access Key field, generate a key.
Copy the generated key and use it as the password for the username.
And then you could refer to Exploring the APIs with Postman and basic authentication.
2021-10-12 Update: The client credential flow is now supported in BC.
There's a little additional setup that's required. The following link will walk you through it:
https://www.kauffmann.nl/2021/07/06/service-to-service-authentication-in-business-central-18-3-how-to-set-up/
It boils down to:
Register the external application in Azure Active Directory
create the application in Azure (paying particular attention to the Redirect URI)
set the required permissions ("Dynamics 365 Business Central / API.ReadWrite.All")
create a secret
Create the external application account in Business Central
add the Client ID from your registered Azure app
add permissions (e.g., "D365 BASIC" and "D365 SALES DOC, EDIT")
grant consent
at that point you can get a token with:
curl --location --request GET 'https://login.microsoftonline.com/<tenant>/oauth2/v2.0/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'client_id=<client>' \
--data-urlencode 'scope=https://api.businesscentral.dynamics.com/.default' \
--data-urlencode 'client_secret=<secret>'
And the customer query works as:
curl --location --request GET 'https://api.businesscentral.dynamics.com/v2.0/<tenant>/<env>/api/v2.0/companies(<company id>)/customers' \
--header 'Authorization: Bearer XYZ...'
Incidentally, I was getting the same Authentication_InvalidCredentials error and it turned out to be tied to the external app in BC not being active.

Why Pocket API returns 403 Forbidden always?

I'm trying to call this line:
curl https://getpocket.com/v3/oauth/authorize --insecure -X POST -H "Content-Type: application/json" -H "X-Accept: application/json" -d "{\"consumer_key\":\"61999-492f79db0bd3292f0b4...1\",\"code\":\"c9166709-0c45-2b1f-a22f-e...r\"}"
and each time I get 403 Forbidden.
I do not know and understand the reason of that.
Does anyone knows? I tried it through Python too:
import requests
auth_params = {'consumer_key': 'key_here', 'redirect_uri': 'https://www.twitter.com/'}
tkn = requests.post('https://getpocket.com/v3/oauth/request', data=auth_params)
tkn.content
Above code gives me a code:
usr_params = {'consumer_key': 'key_here', 'code': 'code_here'}
usr = requests.post('https://getpocket.com/v3/oauth/authorize', data=usr_params)
usr.content
here I'm getting 403 too.
How can I fix that?
From Pocket Authentication API Documentation, you need to register an application to get a consumer key, then request OAuth token via :
curl -X POST \
-H 'Content-Type: application/json' \
-d '{"consumer_key":"XXXXX-XXXXXXXXXXXXXXXXXXXXXX","redirect_uri":"AppTest:authorizationFinished"}' \
https://getpocket.com/v3/oauth/request
Then the step 2 is to authorize this request token (this is the step you are missing). On a browser open the following URL with the request token you got from the previous step :
https://getpocket.com/auth/authorize?request_token=XXXXXXXX-XXXX-XXXX-XXXX-XXXX&redirect_uri=AppTest:authorizationFinished
Click on "authorize" :
Once the request token is authorized, you can call your request on https://getpocket.com/v3/oauth/authorize to convert a request token into a Pocket access token:
curl -X POST \
-H 'Content-Type: application/json' \
-d '{"consumer_key":"XXXXX-XXXXXXXXXXXXXXXXXXX","code":"XXXXXXXXX-XXXX-XXXX-XXXX-XXXXX"}' \
https://getpocket.com/v3/oauth/authorize
The consumer key is the one you got when you created the app on Pocket and the request token the one generated from v3/oauth/request endpoint
Then you get as expected :
{ "access_token":"5678defg-5678-defg-5678-defg56", "username":"pocketuser" }

django-oauth2-provider: get the token without sending the client_secret

I'm using django-oauth2-provider with rest-framework to provide authentication in my API.
My mobile app is going to connect to my REST API to retrieve some data. No third party apps are going to get involved in the process.
According to this, the grant type required for this use case would be the password grant. Since it's a bad idea to store the secret in the device, I need to access the token without it.
I tried to send a request without the secret:
curl -X POST -d "client_id=MY_CLIENT_ID&grant_type=password&username=user&password=pass" http://localhost:8000/oauth2/access_token/
But the response I get is:
{"error": "invalid_client"}
My question is whether it is possible to do this with django-oauth2-provider, and how to do it.
Set Authorization Grant Type as Resource owner password-based
Put WSGIPassAuthorization On at same place as WSGIScriptAlias
You need to create a client through django admin UI and replace "MY_CLIENT_ID" with the ID.
"Client type" of application should be "public"
Just to combine solutions. This is what worked for me. Follow through on the Getting Started guide. However, on creating the application, provide the following:
Name: A name of your choosing
Client Type: Public
Authorization Grant Type: Resource owner password-based
Then the request should be:
curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "grant_type=password&username=<username>&password=<password>&client_id=<client_id>" http://localhost:8000/o/token/
or, if JSON,
to settings.py add:
OAUTH2_PROVIDER = {
# expect request body Content Type application/json
'OAUTH2_BACKEND_CLASS': 'oauth2_provider.oauth2_backends.JSONOAuthLibCore'
}
curl -X POST \
http://localhost:8000/o/token/ \
-H 'Content-Type: application/json' \
-d '{
"grant_type": "password",
"client_id": "<client_id>",
"username": "<username>",
"password": "<password>"
}'
You should use password grant type. The following curl command works with django-oauth-toolkit. I believe it should work with any other oauth provider as well.
curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d 'grant_type=password&username=user&password=pass&client_id=client_id' 'http://localhost:8000/o/token/'
Please see the following link for more info: https://aaronparecki.com/articles/2012/07/29/1/oauth2-simplified#password

Categories