Securing access to your account and assets

Learn how to securely integrate Filestack into different kinds of apps and avoid bad practices with regards to file security.

Key Points

  • By default, Filestack “apps” don’t have the “security” option enabled
  • In the default mode, you have limited controls against abuse
  • In the default mode, write access is effectively public
  • You should enable the “security” option on your apps
  • If a page or endpoint that sends a policy & signature is not behind your own user auth, whatever action enabled by the policy should be thought of as public
  • Policies should have short expiries and limited scopes
  • Do not expose your app secret or include it in client-side code

Before reading this tutorial, you should read through the security concepts page if you haven’t already done so. The concepts page contains the specifications of our security mechanisms and a brief explanation of best practices. The aim of this tutorial is to elaborate on those best practices and provide practical examples of how to follow them.

Problem

By default, each app does not have the “security” option enabled. By default, all that’s needed to upload a file is an API key, and all that’s needed to read a file is its handle. With a few exceptions, all operations and features are available with just this information.

How someone could obtain these values varies with your specific implementation, but it’s generally easy to find them. Handles are included as part of the URL to serve assets, so someone could inspect img elements for the src value, for example. API keys are similarly easy to find, they can be seen by inspecting network requests within the browser. In general, these values should not be thought of as private information, as they’re readily exposed out of necessity.

With a “public” app, you don’t have control over usage. A malicious actor could generate disruptive amounts of uploads, transformations, or downloads, and you would have limited control to stop them. You also have limited control over who has continued access to content. Once you serve someone a handle, they have unrestricted access to download the file and your only control is to delete the file. Unrestricted access could lead to unexpected overage costs and disruption to business.

Solution

With a “private” app (“security” option enabled), you have a mechanism to control access and prevent abuse. In this mode, requests now require policy (authorization) and signature (authentication) pairs. Policies can (and should) be short-lived and limited-scope. Since the app secret is needed to sign policies, but can remain safely on the backend, the problem of exposing access values client-side is solved; API keys and file handles are still exposed, but they don’t grant access to operations on their own.

Example Code

The below example builds a simple Python Flask app. It has two routes, one that renders a simple page containing the web picker, and another that returns credentials as JSON. The first route demonstrates how you could deliver credentials for a server-side app, and the second demonstrates how you could deliver credentials for an SPA or native app. Notice that the credentials we build are only scoped for an upload (the operation we want to perform in this example) and are only valid for 15 minutes.

We’re running this in the following Docker environment:

FROM python:3.6.5

RUN pip install Flask==1.0.2 filestack-python==2.3.1

WORKDIR /usr/src/app/

COPY *.py ./
COPY templates/* ./templates/

ENV FLASK_APP=app.py
ENV FLASK_RUN_HOST=0.0.0.0
ENV FLASK_RUN_PORT=5000

CMD flask run

Template file for the rendered page:

<!DOCTYPE html>
<html>

  <head>
    <meta charset="UTF-8">
    <title>Filestack Demo</title>
    <script src="https://static.filestackapi.com/filestack-js/1.x.x/filestack.min.js"></script>
  </head>

  <body>
    <script>
      function openPicker() {
        // Load from the template variables
        const apiKey = "{{ api_key }}";
        const options = { "security": {} };
        options["security"]["policy"] = "{{ policy }}";
        options["security"]["signature"] = "{{ signature }}";

        const client = filestack.init(apiKey, options);
        client.picker().open();
      }
    </script>
    <button onclick="openPicker();">Select File</button>
  </body>

</html>

And here is our actual application:

import time

from flask import Flask, jsonify, render_template
from filestack import security as create_security

api_key = 'YOUR_API_KEY'
app_secret = 'YOUR_APP_SECRET'
app = Flask(__name__)

# Creates credentials to perform an upload, valid for 15 minutes
def create_upload_creds():
    expiry = int(time.time()) + 15 * 60 # Policy is valid for 15 minutes
    json_policy = { 'call': ['pick'], 'expiry': expiry }
    sec = create_security(json_policy, app_secret)
    sec['policy'] = sec['policy'].decode('ascii')
    creds = { 'api_key': api_key }
    creds['policy'] = sec['policy']
    creds['signature'] = sec['signature']
    return creds

# This endpoint should be behind user auth
# Renders a simple page that contains the web picker
@app.route('/')
def render_picker():
    creds = create_upload_creds()
    return render_template('picker.html', **creds)

# This endpoint should be behind user auth
# Returns credentials as JSON
@app.route('/json')
def get_upload_creds():
    creds = create_upload_creds()
    return jsonify(creds)