Custom access control for files in R2 using D1 and Workers
This tutorial gives you an overview on how to create a TypeScript-based Cloudflare Worker which allows you to control file access based on a simple username and password authentication. To achieve this, we will use a D1 database for user management and an R2 bucket for file storage.
The following sections will guide you through the process of creating a Worker using the Cloudflare CLI, creating and setting up a D1 database and R2 bucket, and then implementing the functionality to securely upload and fetch files from the created R2 bucket.
Prerequisites
- Sign up for a Cloudflare account ↗.
- Install
Node.js
↗.
Node.js version manager
Use a Node version manager like Volta ↗ or nvm ↗ to avoid permission issues and change Node.js versions. Wrangler, discussed later in this guide, requires a Node version of 16.17.0
or later.
1. Create a new Worker application
To get started developing your Worker you will use the create-cloudflare
CLI ↗. To do this, open a terminal window and run the following command:
For setup, select the following options:
- For What would you like to start with?, choose
Hello World example
. - For Which template would you like to use?, choose
Hello World Worker
. - For Which language do you want to use?, choose
TypeScript
. - For Do you want to use git for version control?, choose
Yes
. - For Do you want to deploy your application?, choose
No
(we will be making some changes before deploying).
Then, move into your newly created Worker:
2. Create a new D1 database and binding
Now that you have created your Worker, next you will need to create a D1 database. This can be done through the Cloudflare Portal or the Wrangler CLI. For this tutorial, we will use the Wrangler CLI for simplicity.
To create a D1 database, just run the following command.
If you get asked to install wrangler, just confirm by pressing y
and then press Enter
.
Replace <YOUR_DATABASE_NAME>
with the name you want to use for your database. Keep in mind that this name can’t be changed later on.
After the database is successfully created, you will see the data for the binding displayed as an output.
The binding declaration will start with [[d1_databases]]
and contain the binding name, database name and ID.
To use the database in your worker, you will need to add the binding to your wrangler.toml
file, by copying the declaration and pasting it into the wrangler file, as shown in the example below.
3. Create R2 bucket and binding
Now that the D1 database is created, you also need to create an R2 bucket which will be used to store the uploaded files. This step can also be done through the Cloudflare Portal, but as before, we will use the Wrangler CLI for this tutorial. To create an R2 bucket, run the following command:
This works similar to the D1 database creation, where you will need to replace <YOUR_BUCKET_NAME>
with the name you want to use for your bucket.
To do this, go to the wrangler.toml
file again and then add the following lines:
Now that you have prepared the Wrangler configuration, you should update the worker-configuration.d.ts
file to include the new bindings.
This file will then provide TypeScript with the correct type definitions for the bindings, which allows for type checking and code completion in your editor.
You could either update it manually or run the following command in the directory of your project to update it automatically based on the wrangler configuration file (recommended).
4. Database preparation
Before you can start developing the Worker, you need to prepare the D1 database.
For this you need to
- Create a table in the database which will then be used to store the user data
- Create a unique index on the username column, which will speed up database queries and ensure that the username is unique
- Insert a test user into the table, so you can test your code later on
As this operation only needs to be done once, this will be done through the Wrangler CLI and not in the Worker’s code.
Copy the commands listed below, replace the placeholders and then run them in order to prepare the database.
For this tutorial you can replace the <YOUR_USERNAME>
and <YOUR_HASHED_PASSWORD>
placeholders with admin
and 5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8
respecively.
And <YOUR_DATABASE_NAME>
should be replaced with the name you used to create the database.
5. Implement authentication in the Worker
Now that the database and bucket are all set up, you can start to develop the Worker application. The first thing you will need to do is to implement the authentication for the requests.
This tutorial will use a simple username and password authentication, where the username and password (hashed) are stored in the D1 database.
The requests will contain the username and password as a base64 encoded string, which is also called Basic Authentication.
Depending on the request method, this string will be retrieved from the Authorization
header for POST requests or the Authorization
search parameter for GET requests.
To handle the authentication, you will need to replace the current code within index.ts
file with the following code:
The code above currently extracts the username and password from the request, but does not yet check if the username and password are correct.
To check the username and password, you will need to hash the password and then query the D1 database table user
with the given username and hashed password.
If the username and password are correct, you will retrieve a record from D1. If the username or password is incorrect, undefined will be returned and a 401 Unauthorized
response will be sent.
To add this functionality, you will need to add the following code to the fetch
function by replacing the TODO comment from the last code snippet:
This code will now ensure that every request is authenticated before it can be processed further.
6. Upload a file through the Worker
Now that the authentication is set up, you can start to implement the functionality for uploading a file through the Worker.
To do this, you will need to add a new code path that handles HTTP POST
requests.
Then within it, you will need to get the data from the request, which is sent within the body of the request, by using the request.blob()
function.
After that, you can upload the data to the R2 bucket by using the env.BUCKET.put
function.
And finally, you will return a 200 OK
response to the client.
To implement this functionality, you will need to replace the TODO comment from the last code snippet with the following code:
This code will now allow you to upload a file through the Worker, which will be stored in your R2 bucket.
7. Fetch from the R2 bucket
To round up the Worker application, you will need to implement the functionality to fetch files from the R2 bucket.
This can be done by adding a new code path that handles GET
requests.
Within this code path, you will need to extract the URL pathname and then retrieve the asset from the R2 bucket by using the env.BUCKET.get
function.
To finalize the code, just replace the TODO comment for handling GET requests from the last code snippet with the following code:
This code now allows you to fetch and return data from the R2 bucket when a GET
request is made to the Worker application.
8. Deploy your Worker
After completing the code for this Cloudflare Worker tutorial, you will need to deploy it to Cloudflare. To do this open the terminal in the directory created for your application, and then run:
You might get asked to authenticate (if not logged in already) and select an account. After that, the Worker will be deployed to Cloudflare. When the deployment finished successfully, you will see a success message with the URL where your Worker is now accessible.
9. Test your Worker (optional)
To finish this tutorial, you should test your Worker application by sending a POST
request to upload a file and after that a GET
request to fetch the file.
This can be done by using a tool like curl
or Postman
, but for simplicity, this will describe the usage of curl
.
Copy the following command which can be used to upload a simple JSON file with the content {"Hello": "Worker!"}
.
Replace <YOUR_API_SECRET>
with the base64 encoded username and password combination and then run the command. For this example you can use YWRtaW46cGFzc3dvcmQ=
, which can be decoded to admin
and test
, for the api secret placeholder.
Then run the next command, or simply open the URL in your browser, to fetch the file you just uploaded:
Next steps
If you want to learn more about Cloudflare Workers, R2, or D1 you can check out the following documentation: