BoxOAuthException when using JWT

Hello,

I am trying to access my Box app with Python code, that is using JWT to authenticate. Here is my code:

from boxsdk import JWTAuth
from boxsdk import Client
from cryptography.hazmat.primitives.serialization import load_pem_private_key
import json
import base64
private_key_fpath = "/path/to/PrivatKey.pem"
#Settings below generated by the Box Developer Console
settings_fpath = "/path/to/1234__config.json"
with open(private_key_fpath, "rb") as key_file:
    priv_rsakey = load_pem_private_key(key_file.read(), password=None)
with open(settings_fpath, "r") as f:
    settings = json.loads(f.read())
settings['boxAppSettings']['appAuth']["privateKey"] = priv_rsakey
auth = JWTAuth.from_settings_dictionary(
    settings
)
access_token = auth.authenticate_instance()

I get the following error

BoxOAuthException: 
Message: "kid" invalid, unable to lookup correct key
Status: 400
URL: https://api.box.com/oauth2/token
Method: POST
Headers: {'Date': 'Mon, 22 Apr 2024 20:27:23 GMT', 'Content-Type': 'application/json', 'Strict-Transport-Security': 'max-age=31536000', 'Set-Cookie': 'box_visitor_id=6626c5ed677b62.17237192; expires=Tue, 22-Apr-2025 20:27:23 GMT; Max-Age=31536000; path=/; domain=.box.com; secure; SameSite=None, bv=MONO-6104; expires=Mon, 29-Apr-2024 20:27:23 GMT; Max-Age=604800; path=/; domain=.app.box.com; secure, cn=53; expires=Tue, 22-Apr-2025 20:27:23 GMT; Max-Age=31536000; path=/; domain=.app.box.com; secure, site_preference=desktop; path=/; domain=.box.com; secure', 'Cache-Control': 'no-store', 'Via': '1.1 google', 'Alt-Svc': 'h3=":443"; ma=2592000,h3-29=":443"; ma=2592000', 'Transfer-Encoding': 'chunked'}

Is my code missing anything?

Thanks!

Hi @alex_resolver ,

Well the error message says you’re missing the public key id, and that should be included in your config.json, or manually added as you’re doing with the private key.

For example:

{
  "boxAppSettings": {
    "clientID": "fe...so",
    "clientSecret": "3N...i",
    "appAuth": {
      "publicKeyID": "39749s1s",
      "privateKey": "-----BEGIN ENCRYPTED PRIVATE KEY-----\nMII...nlCE=\n-----END ENCRYPTED PRIVATE KEY-----\n",
      "passphrase": "fa...c3"
    }
  },
  "enterpriseID": "877840855",
  "webhooks": {
    "primaryKey": "zX...E0",
    "secondaryKey": "Ew...nf"
  }
}

That public key id must match what is configure in your application developer console.
For example:

My config.json was downloaded at the time of key creation. If you didn’t save that file, or created the key manually, it is ok, you can download the config.json file:

However the file will be incomplete since Box does not store your private key, and the file is also missing the key id:

For example if I download my config.json:

{
  "boxAppSettings": {
    "clientID": "fe...so",
    "clientSecret": "3N...xi",
    "appAuth": {
      "publicKeyID": "",
      "privateKey": "",
      "passphrase": ""
    }
  },
  "enterpriseID": "877840855",
  "webhooks": {
    "primaryKey": "zX...E0",
    "secondaryKey": "Ew...nf"
  }
}

From here I believe your have 2 options, edit your config.json and add the missing key id, or manually add the key id via settings.

You can also decide to add the private key to your config.json file, watch out for the explicit EOL \n.

On a side note, it is not clear to me if you created the private key manually, and most importantly if it is encrypted or not. Make sure it is consistent, if encrypted, then check the passphrase.

The SDK does handle the steps of reading the private key file.
You can try something like this:

from boxsdk import JWTAuth, Client

service_account_auth = JWTAuth(
    client_id='YOUR_CLIENT_ID',
    client_secret='YOUR_CLIENT_SECRET',
    enterprise_id='YOUR_ENTERPRISE_ID',
    jwt_key_id='YOUR_JWT_KEY_ID',
    rsa_private_key_file_sys_path='/path/to/file/CERT.PEM',
    rsa_private_key_passphrase='PASSPHRASE',
    store_tokens=your_store_tokens_callback_method,
)

access_token = auth.authenticate_instance()

service_account_client = Client(auth)

Or, if the config.json is complete:

auth = JWTAuth.from_settings_file('/path/to/settings.json')
client = Client(auth)
service_account = client.user().get()
print(f'Service Account user ID is {service_account.id}')

Let us know if this helps

Best regards