cancel
Showing results for 
Search instead for 
Did you mean: 
Announcements

The Hub is now in read-only mode as we make improvements to the Hub experience. More information is available here.

Authentication Scope

Wildwizard
Mt. Kenya

Hi Folks,

Trying to use the API to upload activities recorded by a Chinese Bicycle Computer, that junk product cannot automatically upload data to Strava, so I have to write a script to get those fit files via adb, and then upload them to strava via api.

However I got response like this:

Upload failed: {"message":"Authorization Error","errors":[{"resource":"AccessToken","field":"activity:write_permission","code":"missing"}]}

I used

scope = ['activity:write']

when getting an access_token, and I found out I always get the same access token whatever I set the scope to be.

That access token is also the one showed on https://www.strava.com/settings/api, under that I can see scope: read, just like https://communityhub.strava.com/t5/developer-discussions/authentication-scope/m-p/6357In order to upload fit files I need a token with "activity:write", but I cannot find anywhere to change the scope of my access token.

Of course I tried to solved this by https://developers.strava.com/docs/authentication/ , however its not helping well. 

here is my demo written by python

from requests_oauthlib import OAuth2Session
import requests


def get_access_token():
client_id = "client_id"
client_secret = "client_secret"
refresh_token = "refresh_token "
scope = ["activity:write"]

oauth = OAuth2Session(client_id=client_id, redirect_uri="http://localhost", scope=scope)
token_url = "https://www.strava.com/oauth/token"
extra = {
"client_id": client_id,
"client_secret": client_secret,
}
token = oauth.refresh_token(token_url, refresh_token=refresh_token, **extra)
print(token)

return token["access_token"]

access_token = get_access_token()
headers = {'Authorization': 'Bearer ' + access_token}

activity_type = 'ride' # or 'run', 'swim', etc.
name = 'activity20230503172414'
description = 'upload by api'
commute = False # whether this activity is a commute
trainer = False # whether this activity was done on a trainer
data_type = 'fit'

url = 'https://www.strava.com/api/v3/uploads'

fit_file = open('20230503172414.fit', 'rb')
files = {'file': ('activity.fit', fit_file, 'application/octet-stream')}

params = {
'data_type': data_type,
'activity_type': activity_type,
'name': name,
'description': description,
'commute': commute,
'trainer': trainer
}

response = requests.post(url, headers=headers, data=params, files=files)

if response.status_code == 200:
print('Upload successful')
else:
print('Upload failed:', response.text)
1 ACCEPTED SOLUTION

Here, I reproduced it:

 

from requests_oauthlib import OAuth2Session
client_id = 'xxxx'
client_secret = 'yyyy'
redirect_uri = 'http://localhost'
scope = ['activity:write']
oauth = OAuth2Session(client_id, redirect_uri=redirect_uri, scope=scope)
print('Please go to %s and authorize access.' % authorization_url)
Please go to https://www.strava.com/oauth/authorize?response_type=code&client_id=xxxx&redirect_uri=http%3A%2F%2Flocalhost&scope=activity%3Awrite&state=... and authorize access.

 

This led me to the following screen in my browser (you should see some info. about your app there instead):

paleloser_0-1683195748123.png

And clicking in "Authorize" gives me the following URI: http://localhost/?state=...&code=zzzz&scope=read,activity:write

Which I can now exchange for an access_token. I couldn't do this part with the library you're using, as it told me the response by the authentication endpoint was wrong:

 

token = oauth.fetch_token('https://www.strava.com/oauth/token', authorization_response='https://localhost?state=...&code=zzzz&scope=read,activity:write', client_secret=client_secret)
Traceback (most recent call last):
  [...]
oauthlib.oauth2.rfc6749.errors.MissingTokenError: (missing_token) Missing access token parameter.

 

However, I managed to do this with cURL:

 

curl -X POST https://www.strava.com/api/v3/oauth/token \
  -d client_id=xxxx \              
  -d client_secret=yyyy \
  -d code=zzzz \
  -d grant_type=authorization_code
{"token_type":"Bearer","expires_at":1683217836,"expires_in":21600,"refresh_token":"...","athlete":{"id":25332098,"username":"pcaraballollorente","resource_state":2,"firstname":"Pablo","lastname":"Caraballo Llorente","bio":"","city":"Madrid","state":"ESPAÑA","country":null,"**bleep**":"M","premium":false,"summit":false,"created_at":"2017-09-28T07:50:11Z","updated_at":"2023-04-25T15:40:29Z","badge_type_id":0,"weight":75.0,"profile_medium":"https://dgalywyr863hv.cloudfront.net/pictures/athletes/25332098/24670087/1/medium.jpg","profile":"https://dgalywyr863hv.cloudfront.net/pictures/athletes/25332098/24670087/1/large.jpg","friend":null,"follower":null}}

 

Hope that helps.

 

 

View solution in original post

6 REPLIES 6

Wildwizard
Mt. Kenya

Thanks a lot for for all the suggestions. However, I ended up discarding that unreliable bike computer and purchasing a new one from a different brand. The previous model I had, the Xoss Nav, didn't function well with the Sigeyi power meter. It had frequent issues with data loss and connectivity. Therefore, I wouldn't recommend buying the Xoss computer if you have a dual-side power meter. Xoss recently released a new model called Nav Plus, but I am not impressed with it. Fortunately, my new computer has resolved all the aforementioned problems, so I no longer need to develop any scripts.

paleloser
Pico de Orizaba

First of all, I want to make clear that I'm not very experienced with OAuth2.

According to the authentication flow described in this diagram. In order to get an authorization code, which can be later exchanged by an access_token, a client/user firstly needs to let your app perform requests on their behalf. Even if that client/user is you, the app owner.

Thus, I'd say you may want to follow something as described in your library for the Web Application Flow:

  1. To begin the flow you need the client_id, client_secret, scope, redirect_uri, and authorization_uri (which you already have in your script).
  2. The authorization_response will contain the authorization_code in the query parameters. But your library here seems to make it even smoother and accepts passing the whole response to fetch_token
  3. Calling fetch_token should provide you the access_token, and all of the info. you need to refresh it any time.

Here, I reproduced it:

 

from requests_oauthlib import OAuth2Session
client_id = 'xxxx'
client_secret = 'yyyy'
redirect_uri = 'http://localhost'
scope = ['activity:write']
oauth = OAuth2Session(client_id, redirect_uri=redirect_uri, scope=scope)
print('Please go to %s and authorize access.' % authorization_url)
Please go to https://www.strava.com/oauth/authorize?response_type=code&client_id=xxxx&redirect_uri=http%3A%2F%2Flocalhost&scope=activity%3Awrite&state=... and authorize access.

 

This led me to the following screen in my browser (you should see some info. about your app there instead):

paleloser_0-1683195748123.png

And clicking in "Authorize" gives me the following URI: http://localhost/?state=...&code=zzzz&scope=read,activity:write

Which I can now exchange for an access_token. I couldn't do this part with the library you're using, as it told me the response by the authentication endpoint was wrong:

 

token = oauth.fetch_token('https://www.strava.com/oauth/token', authorization_response='https://localhost?state=...&code=zzzz&scope=read,activity:write', client_secret=client_secret)
Traceback (most recent call last):
  [...]
oauthlib.oauth2.rfc6749.errors.MissingTokenError: (missing_token) Missing access token parameter.

 

However, I managed to do this with cURL:

 

curl -X POST https://www.strava.com/api/v3/oauth/token \
  -d client_id=xxxx \              
  -d client_secret=yyyy \
  -d code=zzzz \
  -d grant_type=authorization_code
{"token_type":"Bearer","expires_at":1683217836,"expires_in":21600,"refresh_token":"...","athlete":{"id":25332098,"username":"pcaraballollorente","resource_state":2,"firstname":"Pablo","lastname":"Caraballo Llorente","bio":"","city":"Madrid","state":"ESPAÑA","country":null,"**bleep**":"M","premium":false,"summit":false,"created_at":"2017-09-28T07:50:11Z","updated_at":"2023-04-25T15:40:29Z","badge_type_id":0,"weight":75.0,"profile_medium":"https://dgalywyr863hv.cloudfront.net/pictures/athletes/25332098/24670087/1/medium.jpg","profile":"https://dgalywyr863hv.cloudfront.net/pictures/athletes/25332098/24670087/1/large.jpg","friend":null,"follower":null}}

 

Hope that helps.

 

 

Thank you so much for all the suggestions. However, I ended up discarding that unreliable bike computer and purchasing a new one from a different brand. The previous model I had, the Xoss Nav, didn't function well with the Sigeyi power meter. It had frequent issues with data loss and connectivity. Therefore, I wouldn't recommend buying the Xoss computer if you have a dual-side power meter. Xoss recently released a new model called Nav Plus, but I am not impressed with it. Fortunately, my new computer has resolved all the aforementioned problems, so I no longer need to develop any scripts.

LeoGanz
Mt. Kenya

Maybe the fault lies within using an array to specify the scope. The API documentation states

"Requested scopes, as a comma delimited string, e.g. "activity:read_all,activity:write". Applications should request" https://developers.strava.com/docs/authentication/

Take a look at what your request URL looks like. I would try using 

scope = "activity:write"

 instead of

scope = ["activity:write"]

Wildwizard
Mt. Kenya

Or maybe I should try Swagger Playground?