Connect to my own Box account via Python SDK
AnsweredMy team and I share a Box instance at the office, which we do not administer. We all use our browser to upload and download files to and from this instance.
We want to do the same using the SDK (in our case Python) so that we can automate actions with Box and treat it like a general shared filesystem for our scripts.
I set up my own free Box account. I wrote the code to connect via JWT, and was able to upload and download, but then I realized that's for a special app, not the general Box account I set up.
I see the instructions for OAuth2, which includes a step where the user grants access to Box via browser. For automation that won't work, unless that grant is just a one-time thing that then stays in place indefinitely.
I can see in the page that requests the grant this code:
" name="__login" value="1"><input type="hidden"
name="redirect_url" value="/api/oauth2/authorize?
state=box_csrf_token_UWrA4Neo2kzUvoYg&
response_type=code&
client_id=ogdx613sjyag1daqg6fejegs12hyhqo9&
redirect_uri=https%3A%2F%2Fapp.box.com">
<input type="hidden"
name="request_token" value="e8b74bfb3f74e7192705acb5k315170d2cf6f019e3bd060bc16d3634b1a32c9b">
<input type="hidden" id="_pw_sql" name="_pw_sql" value=""/>
The instructions at https://developer.box.com/guides/authentication/oauth2/with-sdk/ say something about a short-lived code that can be used to generate an access token. Is the value for request_token (e8b74bfb3f74e7192705acb5k315170d2cf6f019e3bd060bc16d3634b1a32c9b) this short-lived code?
If yes, the documentation then says:
oauth.authenticate('[CODE]')
client = Client(oauth)
But it does not define oauth. What is that?
-
Official comment
Hello Jason,
Since you said you're trying to write scripts for automate tasks, you will want to use JWT authentication, which does not require end-user interaction.
Could you clarify what you meant when you said, "I wrote the code to connect via JWT, and was able to upload and download, but then I realized that's for a special app, not the general Box account I set up." ?
It sounds like you may have been using the default token for a JWT app, which is the JWT service account
If you set your apps application access to "enterprise" you can obtain a token for a regular Box user account. To do this, you'll want to ensure that the following values are set in your JWT assertion:
box_sub_type = user
sub = userID of the user you want a token for
Best,
Kourtney
Comment actions -
Hello Kourtney,
What I meant by "I wrote the code to connect via JWT, and was able to upload and download, but then I realized that's for a special app, not the general Box account I set up" is that the files I upload via the JWT service account were only visible when logged in via the JWT service account (via my Python script). They were not visible from a browser pointed at https://app.box.com/folder/0 when logged in as my user js******08@gmail.com
So, following your instructions above I have this code:
authentication_url = 'https://api.box.com/oauth2/token'
which yields:
user_id = "js*****08@gmail.com"
config_file = pathlib.Path.home() / "config.json"
config = json.load(open(config_file))
appAuth = config["boxAppSettings"]["appAuth"]
privateKey = appAuth["privateKey"]
passphrase = appAuth["passphrase"]
key = load_pem_private_key(
data=privateKey.encode('utf8'),
password=passphrase.encode('utf8'),
backend=default_backend(),
)
claims = {
'iss': config['boxAppSettings']['clientID'],
'sub': user_id,
'box_sub_type': 'user',
'aud': authentication_url,
'jti': secrets.token_hex(64),
'exp': round(time.time()) + 45
}
keyId = config['boxAppSettings']['appAuth']['publicKeyID']
assertion = jwt.encode(
claims,
key,
algorithm='RS512',
headers={
'kid': keyId
}
)
params = {
'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
'assertion': assertion,
'client_id': config['boxAppSettings']['clientID'],
'client_secret': config['boxAppSettings']['clientSecret']
}
response = requests.post(authentication_url, params)
print(response.status_code)
print(response.text)400
{"error":"invalid_request","error_description":"Cannot obtain token based on the enterprise configuration for your app"}In my app configuration I have checked "OAuth 2.0 with JSON Web Tokens" for Authentication Method, "Enterprise" for Application Access, enabled all Application Scopes, and enabled all Advanced Features.And, just to be clear, my user_id is my email address (js*****08@gmail.com) I use when logging in via a browser? -
Hey Jason,
Your user_id is actually going to be a unique numeric value that is assigned to each Box account. An easy way to obtain this is using the get current user endpoint, but to make this really easy for you yours is 14239712463. Let me know if you still encounter errors after changing the email address in your code to the user ID number!
Best,
Kourtney
-
Thank you for sticking with me, Kourtney. The snippet of my code that is changed:
claims = {
'iss': config['boxAppSettings']['clientID'],
'sub': "14239712463",
'box_sub_type': 'user',
'aud': authentication_url,
'jti': secrets.token_hex(64),
'exp': round(time.time()) + 45
}And the same result:400
{"error":"invalid_request","error_description":"Cannot obtain token based on the enterprise configuration for your app"} -
That did it! Thank you, Kourtney.
Three things ... first, resubmitting did not generate a new approval email, but I searched through my old email for the original one and clicked the link in that email
Second, here's my working code for the next gal/guy who needs this:
import cryptography
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.serialization import load_pem_private_key
import io
import json
import os
import pathlib
import time
import secrets
import boxsdk
import requests
import jwt
authentication_url = 'https://api.box.com/oauth2/token'
account_id = "00000000000" # set to your account_id
config_file = pathlib.Path.home() / "config.json"
config = json.load(open(config_file))
appAuth = config["boxAppSettings"]["appAuth"]
privateKey = appAuth["privateKey"]
passphrase = appAuth["passphrase"]
# https://cryptography.io/en/latest/
key = load_pem_private_key(
data=privateKey.encode('utf8'),
password=passphrase.encode('utf8'),
backend=default_backend(),
)
claims = {
'iss': config['boxAppSettings']['clientID'],
'sub': account_id
'box_sub_type': 'user',
'aud': authentication_url,
'jti': secrets.token_hex(64),
'exp': round(time.time()) + 45
}
keyId = config['boxAppSettings']['appAuth']['publicKeyID']
assertion = jwt.encode(
claims,
key,
algorithm='RS512',
headers={
'kid': keyId
}
)
params = {
'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
'assertion': assertion,
'client_id': config['boxAppSettings']['clientID'],
'client_secret': config['boxAppSettings']['clientSecret']
}
response = requests.post(authentication_url, params)
access_token = response.json()['access_token']
auth = boxsdk.OAuth2(
client_id=config['boxAppSettings']['clientID'],
client_secret='',
access_token=access_token
)
client = boxsdk.Client(auth)
root_folder = client.folder('0')
for item in root_folder.get_items():
my_file = client.file(file_id=item.id)
print(my_file)Third, these are screenshots of my app configuration:
Note that "account_id" above is actually User ID below.
Please sign in to leave a comment.
Comments
6 comments