Hello Everyone,
The target audience of this post is limited.
In this article of mine, I will write down the steps of how you can connect with Box Explorer Component to allow users to upload file into your Box account.
Requirement: We want to allow our users to upload file to somewhere in the cloud where we (me and my team) can see the files and take further actions on it
There are many solutions to this: You have AWS S3, DropBox, Google Cloud and lots more but if you do not want to spend much time on designing and engineering the front-end (by designing I mean building the entire UI for file management), you can use Box.com’s easy to integrate Box Explorer Component.
Related links:
- https://developer.box.com/guides/embed/ui-elements/explorer/
- https://app.box.com/developers/console
- https://app.box.com/master
- https://github.com/box-community/samples-docs-authenticate-with-jwt-api/blob/master/sample.rb
Prerequisites:
- Ruby >= 2
- Rails >= 5
- A Box.com account
- A Box.com App – You can create the same from Developer’s console (see Related links#2)
- Make sure your app is authorised. You can do the same from here: https://app.box.com/master/custom-apps
- A config.json file that you can download from: https://app.box.com/developers/console/app/:app_id/configuration. You’ll have to generate Public/Private keypair for this – You can do the same from the same page.
- You’ll have to add your domain to allowed origins via above link.
Sample Output / Demo: https://codepen.io/box-platform/pen/wdWWdN. In addition you’ll also see options to add / upload file / folder and perform operations like Search for uploads, Download, Share, Delete etc.
Once you are done setting up above things, let’s code this:
# lib/box_api.rb
require 'json'
require "openssl"
require 'securerandom'
require 'jwt'
require 'json'
require 'uri'
require 'net/https'
class BoxApi
def initialize unique_identifier = nil
@unique_identifier = unique_identifier
@access_token = Rails.cache.fetch("users/#{@unique_identifier}/box/access_token")
end
def get_token
return @access_token unless @access_token.nil?
set_auth
# Make the request
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)
# Parse the JSON and extract the access token
@access_token = Rails.cache.fetch("users/#{@unique_identifier}/box/access_token", expires_in: 60.minutes) do
JSON.parse(response.body)['access_token']
end
return @access_token
end
def find_or_create_folder_by_folder_name folder_name = @unique_identifier
root_folder = find_folder "Client Uploads"
root_folder = create_folder "Client Uploads" if !root_folder
child_folder = find_folder folder_name, root_folder["id"]
child_folder = create_folder folder_name, root_folder["id"] if !child_folder
return child_folder["id"]
end
def find_folder folder_name, parent_folder_id = nil
base_url = "https://api.box.com/2.0/search?query='#{folder_name}'&type=folder"
base_url += "&ancestor_folder_ids=#{parent_folder_id}" if parent_folder_id
uri = URI.parse(base_url)
request = Net::HTTP::Get.new(uri)
request["Authorization"] = "Bearer #{@access_token}"
req_options = {
use_ssl: uri.scheme == "https",
}
response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
http.request(request)
end
response_body = JSON.parse(response.body)
return false if response_body["total_count"] == 0 && response_body["entries"].empty?
return response_body["entries"][0]
end
def create_folder folder_name, parent_folder_id = nil
uri = URI.parse("https://api.box.com/2.0/folders")
request = Net::HTTP::Post.new(uri)
request.content_type = "application/json"
request["Authorization"] = "Bearer #{@access_token}"
params = {}
params["name"] = folder_name
params["parent"] = {
"id" => parent_folder_id ? parent_folder_id : "0"
}
request.body = JSON.dump(params)
req_options = {
use_ssl: uri.scheme == "https",
}
response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
http.request(request)
end
response_body = JSON.parse(response.body)
return response_body if response.code == "201"
end
private
def set_auth
@config = JSON.parse(
File.read("#{Rails.root.join}/config/box_config.json")
)
@config = Rails.env.production? ? @config["production"] : @config["base"]
@appAuth = @config['boxAppSettings']['appAuth']
key = OpenSSL::PKey::RSA.new(
@appAuth['privateKey'],
@appAuth['passphrase']
)
@authentication_url = 'https://api.box.com/oauth2/token'
claims = {
iss: @config['boxAppSettings']['clientID'],
sub: @config['enterpriseID'],
box_sub_type: 'enterprise',
aud: @authentication_url,
# This is an identifier that helps protect against
# replay attacks
jti: SecureRandom.hex(64),
# We give the assertion a lifetime of 45 seconds
# before it expires
exp: Time.now.to_i + 45
}
keyId = @appAuth['publicKeyID']
# Rather than constructing the JWT assertion manually, we are
# using the pyjwt library.
# The API support "RS256", "RS384", and "RS512" encryption
assertion = JWT.encode(claims, key, 'RS512', { kid: keyId })
# We are using the excellent axios package
# to simplify the API call
@params = URI.encode_www_form({
# This specifies that we are using a JWT assertion
# to authenticate
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
# Our JWT assertion
assertion: assertion,
# The OAuth 2 client ID and secret
client_id: @config['boxAppSettings']['clientID'],
client_secret: @config['boxAppSettings']['clientSecret']
})
end
end
Let’s call this api via controller’s method: `/app/controllers/home_controller.rb`
# /app/controllers/home_controller.rb
class HomeController < ApplicationController
def connect_to_box
api = BoxApi.new(user.company.name)
token = api.get_token
folder_id = api.find_or_create_folder_by_folder_name
respond_to do | wants |
wants.json do
render json: {
success: true,
access_token: token,
folder_id: folder_id
}
end
end
end
end
and finally let’s load token
and folder_id
via Ajax call from view
/app/views/home/files.html.erb
<%= content_for :page_css do %>
<link rel="stylesheet" href="https://cdn01.boxcdn.net/platform/elements/13.0.0/en-US/explorer.css" />
<% end %>
<%= content_for :page_js do %>
<!-- polyfill.io only loads the polyfills your browser needs -->
https://cdn.polyfill.io/v2/polyfill.min.js?features=es6,Intl
https://cdn01.boxcdn.net/platform/elements/13.0.0/en-US/explorer.js
<% end %>
<div class=<%= "#{controller_name}_#{action_name}" %>>
<div id="uploader-panel" class="box-light-blue">
<div class="container">
<div class="row">
<div class="col-md-12">
<div class="text-center result-overlay">
<%= image_tag 'pageloader.svg' %>
</div>
<div class="box-list-container" style="width: 100%; height: 100%; margin: 0; padding: 0; min-width: 320px;"></div>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript">
$(document).ready(function() {
//connecting to box to fetch required token and folder
$.ajax({
url: '/home/connect_to_box',
type: 'POST',
dataType: 'json',
success: function(response) {
if(response['success'] === true){
$("div.result-overlay").hide();
const folderId = response['folder_id'];
const accessToken = response['access_token'];
const logoUrl = "<%= asset_path('logo-blue.jpg') %>";
const contentExplorer = new Box.ContentExplorer();
// for more options visit: https://developer.box.com/guides/embed/ui-elements/uploader/#options
contentExplorer.show(folderId, accessToken, {
container: ".box-list-container",
canDownload: true,
canUpload: true,
canCreateNewFolder: true,
sortBy: "date",
sortDirection: "DESC",
logoUrl: logoUrl,
onClose: null
});
contentExplorer.on('upload', (data) => {
//notify via slack
$.ajax({
url: '/home/upload_notify',
type: 'POST',
beforeSend: function(xhr) {xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'))},
dataType: "json",
data: {files: JSON.stringify(data)}
});
});
contentExplorer.on('error', (data) => {
alert(`Error uploading file with name "${data.file.name}". The error was: "${data.error}"`);
});
}
}
})
});
</script>
Here’s what’s happening:
- When the page loads we are firing Ajax call to get
access_token
andfolder_id
from Box.folder_id
is the ID of the folder where the user will upload all files. We are giving different folder for each user who belongs to different company (see table below). - The call to Box API checks if the access token is present for the company, it will return that else it will connect to Box, get the access token, set the cache and return the token – Important thing to note that the token returned from Box is valid for 60 minutes
- Based on the returned token, we will check if the folder is present for the company’s user or not!
- The last part of the Ajax call I have written will send the slack notification.
User | Company Name | Folder on Box |
User-A | ABC | ABC |
User-B | XYZ | XYZ |
User-C | ABC | ABC |
User-D | A123 | A123 |
That’s it. Hook it up and see it in action.