My Apps

    JWT without SDKs

    JWT without SDKs

    If you are not ready to use any of the official Box SDKs, or an SDK is not available in your language of choice, it is totally possible to use the Box APIs without them.

    This guide will take you through user authentication using JWT without the use of the Box SDKs. JWT authentication is designed for working directly with the Box API without requiring a user to redirect through Box to authorize your application.

    Overview

    To complete a JWT authorization the following steps need to be completed.

    1. Read the configuration file
    2. Decrypt the private key
    3. Create the JWT assertion
    4. Request the Access Token

    At the end of this flow, the application has an Access Token that can be used to make API calls on behalf of the application.

    The access token acquired through JWT is inherently tied to the Service Account for the application. Any API call made with this token will seem to come from this application and will not have access to files and folders from other users without explicitly getting access them.

    It is possible to act as another user using the As-User header or by requesting a User Access Token.

    Prerequisites

    Before we can get started, you will need to have completed the following steps.

    • Create a Box Application within the developer console
    • Create and download the private key configuration file for your application and save it as config.json
    • Ensure your Box Application is approved for usage within your enterprise

    1. Read JSON configuration

    After creating a Box Application there should be a config.json file containing the application's private key and other details. The following is an example.

    config.json
    {
      "boxAppSettings": {
        "clientID": "abc...123",
        "clientSecret": "def...234",
        "appAuth": {
          "publicKeyID": "abcd1234",
          "privateKey": "-----BEGIN ENCRYPTED PRIVATE KEY-----\n....\n-----END ENCRYPTED PRIVATE KEY-----\n",
          "passphrase": "ghi...345"
        }
      },
      "enterpriseID": "1234567"
    }

    To use this object in the application it needs to be read from file.

    .Net
    using System;
    using System.IO;
    using Newtonsoft.Json;
    
    class Config
    {
        public class BoxAppSettings {
            public class AppAuth {
                public string privateKey { get; set; }
                public string passphrase { get; set; }
                public string publicKeyID { get; set; }
            }
            public string clientID { get; set; }
            public string clientSecret { get; set; }
            public AppAuth appAuth { get; set; }
    
        }
        public string enterpriseID { get; set; }
        public BoxAppSettings boxAppSettings { get; set; }
    }
    
    var reader = new StreamReader("config.json");
    var json = reader.ReadToEnd();
    
    var config = JsonConvert.DeserializeObject<Config>(json);
    Java
    import java.io.FileReader;
    
    import com.google.gson.Gson;
    import com.google.gson.GsonBuilder;
    
    class Config {
      class BoxAppSettings {
        class AppAuth {
          String privateKey;
          String passphrase;
          String publicKeyID;
        }
    
        String clientID;
        String clientSecret;
        AppAuth appAuth;
      }
    
      BoxAppSettings boxAppSettings;
      String enterpriseID;
    }
    
    FileReader reader = new FileReader("config.json");
    
    Gson gson = new GsonBuilder().create();
    Config config = (Config) gson.fromJson(reader, Config.class);
    Python
    import json
    import os
    
    config = json.load(open('config.json'))
    Node
    const fs = require("fs");
    
    const config = JSON.parse(fs.readFileSync("config.json"));
    Ruby
    require 'json'
    
    config = JSON.parse(
      File.read('config.json')
    )
    PHP
    $json = file_get_contents('config.json');
    $config = json_decode($json);

    Parsing JSON

    In some programming languages there is more than one way to read and parse JSON from a file. Refer to guides on your preferred programming language for more complete guides, including error handling.

    2. Decrypt private key

    To create the JWT assertion the application needs the private key from the configuration object. This private key is encrypted and requires a passcode to unlock. Both the encrypted key and passcode are provided in the configuration object.

    .Net
    using System.Security.Cryptography;
    using Org.BouncyCastle.OpenSsl;
    using Org.BouncyCastle.Crypto.Parameters;
    using Org.BouncyCastle.Math;
    
    // https://www.bouncycastle.org/csharp/index.html
    class PasswordFinder : IPasswordFinder
    {
      private string password;
      public PasswordFinder(string _password) { password = _password; }
      public char[] GetPassword() { return password.ToCharArray(); }
    }
    
    var appAuth = config.boxAppSettings.appAuth;
    var stringReader = new StringReader(appAuth.privateKey);
    var passwordFinder = new PasswordFinder(appAuth.passphrase);
    var pemReader = new PemReader(stringReader, passwordFinder);
    var keyParams = (RsaPrivateCrtKeyParameters) pemReader.ReadObject();
    
    public RSA CreateRSAProvider(RSAParameters rp)
    {
      var rsaCsp = RSA.Create();
      rsaCsp.ImportParameters(rp);
      return rsaCsp;
    }
    
    public RSAParameters ToRSAParameters(RsaPrivateCrtKeyParameters privKey)
    {
      RSAParameters rp = new RSAParameters();
      rp.Modulus = privKey.Modulus.ToByteArrayUnsigned();
      rp.Exponent = privKey.PublicExponent.ToByteArrayUnsigned();
      rp.P = privKey.P.ToByteArrayUnsigned();
      rp.Q = privKey.Q.ToByteArrayUnsigned();
      rp.D = ConvertRSAParametersField(privKey.Exponent, rp.Modulus.Length);
      rp.DP = ConvertRSAParametersField(privKey.DP, rp.P.Length);
      rp.DQ = ConvertRSAParametersField(privKey.DQ, rp.Q.Length);
      rp.InverseQ = ConvertRSAParametersField(privKey.QInv, rp.Q.Length);
      return rp;
    }
    
    public byte[] ConvertRSAParametersField(BigInteger n, int size)
    {
      byte[] bs = n.ToByteArrayUnsigned();
      if (bs.Length == size)
          return bs;
      if (bs.Length > size)
          throw new ArgumentException("Specified size too small", "size");
      byte[] padded = new byte[size];
      Array.Copy(bs, 0, padded, size - bs.Length, bs.Length);
      return padded;
    }
    
    var key = CreateRSAProvider(ToRSAParameters(keyParams));
    Java
    import java.io.StringReader;
    import java.security.PrivateKey;
    import java.security.Security;
    
    import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
    import org.bouncycastle.jce.provider.BouncyCastleProvider;
    import org.bouncycastle.openssl.PEMParser;
    import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
    import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
    import org.bouncycastle.operator.InputDecryptorProvider;
    import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
    
    // https://www.bouncycastle.org/java.html
    Security.addProvider(new BouncyCastleProvider());
    
    PEMParser pemParser = new PEMParser(
      new StringReader(config.boxAppSettings.appAuth.privateKey)
    );
    Object keyPair = pemParser.readObject();
    pemParser.close();
    
    char[] passphrase = config.boxAppSettings.appAuth.passphrase.toCharArray();
    JceOpenSSLPKCS8DecryptorProviderBuilder decryptBuilder =
      new JceOpenSSLPKCS8DecryptorProviderBuilder().setProvider("BC");
    InputDecryptorProvider decryptProvider
      = decryptBuilder.build(passphrase);
    PrivateKeyInfo keyInfo
      = ((PKCS8EncryptedPrivateKeyInfo) keyPair).decryptPrivateKeyInfo(decryptProvider);
    
    PrivateKey key = (new JcaPEMKeyConverter()).getPrivateKey(keyInfo);
    Python
    from cryptography.hazmat.backends import default_backend
    from cryptography.hazmat.primitives.serialization import load_pem_private_key
    
    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(),
    )
    Node
    let key = {
      key: config.boxAppSettings.appAuth.privateKey,
      passphrase: config.boxAppSettings.appAuth.passphrase
    };
    Ruby
    require "openssl"
    
    appAuth = config['boxAppSettings']['appAuth']
    key = OpenSSL::PKey::RSA.new(
      appAuth['privateKey'],
      appAuth['passphrase']
    )
    PHP
    $private_key = $config->boxAppSettings->appAuth->privateKey;
    $passphrase = $config->boxAppSettings->appAuth->passphrase;
    $key = openssl_pkey_get_private($private_key, $passphrase);

    An alternative to loading private key from file

    the application might not want to keep both the private key and password stored on disk. An alternative option would be to pass in the password as an environment variable, separating the private key from the token used to unlock the key.

    3. Create JWT assertion

    To authenticate to the Box API the application needs to create a signed JWT assertion that can be exchanged for a traditional OAuth 2.0 Access Token.

    A JWT assertion is essentially an encrypted JSON object, consisting of a header, claims, and signature. Let's start by creating the claims, sometimes also referred to as the payload.

    .Net
    using System.IdentityModel.Tokens.Jwt;
    using System.Security.Claims;
    using System.Collections.Generic;
    
    byte[] randomNumber = new byte[64];
    RandomNumberGenerator.Create().GetBytes(randomNumber);
    var jti = Convert.ToBase64String(randomNumber);
    
    DateTime expirationTime = DateTime.UtcNow.AddSeconds(45);
    
    var claims = new List<Claim>{
      new Claim("sub", config.enterpriseID),
      new Claim("box_sub_type", "enterprise"),
      new Claim("jti", jti),
    };
    Java
    import org.jose4j.jwt.JwtClaims;
    
    String authenticationUrl = "https://api.box.com/oauth2/token";
    
    JwtClaims claims = new JwtClaims();
    claims.setIssuer(config.boxAppSettings.clientID);
    claims.setAudience(authenticationUrl);
    claims.setSubject(config.enterpriseID);
    claims.setClaim("box_sub_type", "enterprise");
    claims.setGeneratedJwtId(64);
    claims.setExpirationTimeMinutesInTheFuture(0.75f);
    Python
    import time
    import secrets
    
    authentication_url = 'https://api.box.com/oauth2/token'
    
    claims = {
      'iss': config['boxAppSettings']['clientID'],
      'sub': config['enterpriseID'],
      'box_sub_type': 'enterprise',
      'aud': authentication_url,
      'jti': secrets.token_hex(64),
      'exp': round(time.time()) + 45
    }
    Node
    const crypto = require("crypto");
    
    const authenticationUrl = "https://api.box.com/oauth2/token";
    
    let claims = {
      iss: config.boxAppSettings.clientID,
      sub: config.enterpriseID,
      box_sub_type: "enterprise",
      aud: authenticationUrl,
      jti: crypto.randomBytes(64).toString("hex"),
      exp: Math.floor(Date.now() / 1000) + 45
    };
    Ruby
    require 'securerandom'
    
    authentication_url = 'https://api.box.com/oauth2/token'
    
    claims = {
      iss: config['boxAppSettings']['clientID'],
      sub: config['enterpriseID'],
      box_sub_type: 'enterprise',
      aud: authentication_url,
      jti: SecureRandom.hex(64),
      exp: Time.now.to_i + 45
    }
    PHP
    $authenticationUrl = 'https://api.box.com/oauth2/token';
    
    $claims = [
      'iss' => $config->boxAppSettings->clientID,
      'sub' => $config->enterpriseID,
      'box_sub_type' => 'enterprise',
      'aud' => $authenticationUrl,
      'jti' => base64_encode(random_bytes(64)),
      'exp' => time() + 45,
      'kid' => $config->boxAppSettings->appAuth->publicKeyID
    ];
    ParameterTypeDescription
    iss, requiredStringThe Box Application's OAuth client ID
    sub, requiredStringThe Box Enterprise ID if this app is to act on behalf of the Service Account of that application, or the User ID if this app wants to act on behalf of another user.
    box_sub_type, requiredStringenterprise or user depending on the type of token being requested in the sub claim
    aud, requiredStringAlways https://api.box.com/oauth2/token
    jti, requiredStringA universally unique identifier specified by the application for this JWT. A unique string of at least 16 characters and at most 128 characters.
    exp, requiredIntegerThe Unix time when this JWT is to expire. Can be set to a maximum value of 60 seconds beyond the issue time. It is recommended to set this to less than the maximum allowed.
    iat, optionalIntegerIssued at time. The token cannot be used before this time.
    nbf, optionalIntegerNot before. Not Specifies when the token will start being valid.

    Next, these claims need to be signed using the private key. Depending on the language and library used, the header of the JWT is configured by defining the encryption algorithm and the ID of the public key used to sign the claims.

    .Net
    using Microsoft.IdentityModel.Tokens;
    
    String authenticationUrl = "https://api.box.com/oauth2/token";
    
    var payload = new JwtPayload(
      config.boxAppSettings.clientID,
      authenticationUrl,
      claims,
      null,
      expirationTime
    );
    
    var credentials = new SigningCredentials(
      new RsaSecurityKey(key),
      SecurityAlgorithms.RsaSha512
    );
    var header = new JwtHeader(signingCredentials: credentials);
    
    var jst = new JwtSecurityToken(header, payload);
    var tokenHandler = new JwtSecurityTokenHandler();
    string assertion = tokenHandler.WriteToken(jst);
    Java
    import org.jose4j.jws.AlgorithmIdentifiers;
    import org.jose4j.jws.JsonWebSignature;
    
    JsonWebSignature jws = new JsonWebSignature();
    jws.setPayload(claims.toJson());
    jws.setKey(key);
    
    jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA512);
    jws.setHeader("typ", "JWT");
    jws.setHeader("kid", config.boxAppSettings.appAuth.publicKeyID);
    String assertion = jws.getCompactSerialization();
    Python
    import jwt
    
    keyId = config['boxAppSettings']['appAuth']['publicKeyID']
    
    assertion = jwt.encode(
      claims,
      key,
      algorithm='RS512',
      headers={
        'kid': keyId
      }
    )
    Node
    const jwt = require('jsonwebtoken')
    
    let keyId = config.boxAppSettings.appAuth.publicKeyID
    
    let headers = {
      'algorithm': 'RS512',
      'keyid': keyId,
    }
    
    let assertion = jwt.sign(claims, key, headers)
    Ruby
    require 'jwt'
    keyId = appAuth['publicKeyID']
    assertion = JWT.encode(claims, key, 'RS512', { kid: keyId })
    PHP
    use \Firebase\JWT\JWT;
    $assertion = JWT::encode($claims, $key, 'RS512');

    For the header the following parameters are supported.

    ParameterTypeDescription
    algorithm, requiredStringThe encryption algorithm used to sign the JWT claim. This can be one of RS256, RS384, or RS512.
    keyid, requiredStringThe ID of the public key used to sign the JWT. Not required, though essential when multiple key pairs are defined for an application.

    Using JWT libraries

    Signing your own JWT can be a complicated and painful process. Luckily, the hard work has already been done for you and libraries exist in pretty much every language. Head over to JWT.io for an overview.

    4. Request Access Token

    The final step is to exchange the short lived JWT assertion for a more long lived OAuth 2.0 Access Token by calling the authentication endpoint with the assertion as a parameter.

    .Net
    using System.Net;
    using System.Net.Http;
    
    var content = new FormUrlEncodedContent(new[]
    {
      new KeyValuePair<string, string>(
        "grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer"),
      new KeyValuePair<string, string>(
        "assertion", assertion),
      new KeyValuePair<string, string>(
        "client_id", config.boxAppSettings.clientID),
      new KeyValuePair<string, string>(
        "client_secret", config.boxAppSettings.clientSecret)
    });
    
    var client = new HttpClient();
    var response = client.PostAsync(authenticationUrl, content).Result;
    
    class Token
    {
      public string access_token { get; set; }
    }
    
    var data = response.Content.ReadAsStringAsync().Result;
    var token = JsonConvert.DeserializeObject<Token>(data);
    var accessToken = token.access_token;
    Java
    import java.util.ArrayList;
    import java.util.List;
    
    import org.apache.http.HttpEntity;
    import org.apache.http.NameValuePair;
    import org.apache.http.client.entity.UrlEncodedFormEntity;
    import org.apache.http.client.methods.CloseableHttpResponse;
    import org.apache.http.client.methods.HttpPost;
    import org.apache.http.impl.client.CloseableHttpClient;
    import org.apache.http.impl.client.HttpClientBuilder;
    import org.apache.http.message.BasicNameValuePair;
    import org.apache.http.util.EntityUtils;
    
    List<NameValuePair> params = new ArrayList<NameValuePair>();
    
    params.add(new BasicNameValuePair(
      "grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer"));
    params.add(new BasicNameValuePair(
      "assertion", assertion));
    params.add(new BasicNameValuePair(
      "client_id", config.boxAppSettings.clientID));
    params.add(new BasicNameValuePair(
      "client_secret", config.boxAppSettings.clientSecret));
    
    CloseableHttpClient httpClient =
      HttpClientBuilder.create().disableCookieManagement().build();
    HttpPost request = new HttpPost(authenticationUrl);
    request.setEntity(new UrlEncodedFormEntity(params));
    CloseableHttpResponse httpResponse = httpClient.execute(request);
    HttpEntity entity = httpResponse.getEntity();
    String response = EntityUtils.toString(entity);
    httpClient.close();
    
    class Token {
      String access_token;
    }
    
    Token token = (Token) gson.fromJson(response, Token.class);
    String accessToken = token.access_token;
    Python
    import json
    
    from urllib.request import urlopen
    from urllib.request import Request
    from urllib.parse import urlencode
    
    params = urlencode({
      'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
      'assertion': assertion,
      'client_id': config['boxAppSettings']['clientID'],
      'client_secret': config['boxAppSettings']['clientSecret']
    }).encode()
    
    request = Request(authentication_url, params)
    response = urlopen(request).read()
    access_token = json.loads(response)['access_token']
    Node
    const axios = require('axios')
    const querystring = require('querystring');
    
    let accessToken = await axios.post(
      authenticationUrl,
      querystring.stringify({
        grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
        assertion: assertion,
        client_id: config.boxAppSettings.clientID,
        client_secret: config.boxAppSettings.clientSecret
      })
    )
    .then(response => response.data.access_token)
    Ruby
    require 'json'
    require 'uri'
    require 'net/https'
    
    params = URI.encode_www_form({
      grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
      assertion: assertion,
      client_id: config['boxAppSettings']['clientID'],
      client_secret: config['boxAppSettings']['clientSecret']
    })
    
    uri = URI.parse(authentication_url)
    http = Net::HTTP.start(uri.host, uri.port, use_ssl: true)
    request = Net::HTTP::Post.new(uri.request_uri)
    request.body = params
    response = http.request(request)
    
    access_token = JSON.parse(response.body)['access_token']
    PHP
    use GuzzleHttp\Client;
    
    $params = [
      'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
      'assertion' => $assertion,
      'client_id' => $config->boxAppSettings->clientID,
      'client_secret' => $config->boxAppSettings->clientSecret
    ];
    
    $client = new Client();
    $response = $client->request('POST', $authenticationUrl, [
      'form_params' => $params
    ]);
    
    $data = $response->getBody()->getContents();
    $access_token = json_decode($data)->access_token;

    Summary

    By now the application should be able to authorize an application using JWT without using any of the SDKs, by using the following steps.

    1. Read the configuration file
    2. Decrypt the private key
    3. Create the JWT assertion
    4. Request the Access Token

    To learn how to use this token head over to the guide on Making API calls.

    Code Samples

    All of the code in this guide is available on GitHub.