Authenticate requests with Functions
Guard an Object Storage bucket with a function that validates a JSON Web Token and serves the object only to an authorized request.
You can put a function in front of an Object Storage bucket, so the function validates a JSON Web Token before any object leaves the bucket.
A request that carries a valid token receives the object with its content type. Every other request receives HTTP 401, and the bucket is never read. A bucket that needs no token is served by a connector and a Rules Engine rule instead, which Use a bucket as an application origin covers.
Prerequisites
- An Azion account.
- The Azion CLI installed and authorized.
- Node.js version 18 or higher.
- A bucket that holds the objects to protect, with Workloads Access set to Read Only or Read & Write. A bucket set to Restricted is not read by Azion Runtime, so the function reaches nothing in it. Refer to Create a bucket.
- A signing secret for the tokens your application issues.
How the check runs
The function answers the requests a rule routes to it. It reads the token from the Authorization header or from the auth_token cookie, verifies the signature against the shared secret, and calls Object Storage only after the signature checks out. A bucket is also reachable through the S3 endpoint and through a connector, and neither of those paths asks for a token; the function guards the path it sits on.
The flow below shows the two refusals and the single route that reaches the bucket:
Create the function project
The Azion CLI scaffolds the project and npm adds the library that verifies the token. To set up the project:
At the prompts, set Template to JavaScript and Runtime to Azion Runtime. The Azion CLI writes the project into a my-auth-storage folder.
The jose library verifies JSON Web Tokens in JavaScript runtimes. npm adds it to package.json.
The project folder holds the template source and the jose dependency.
Write the handler
Open the main JavaScript file of the project and replace its contents with this handler:
Six decisions sit in the code:
- The object key is the request path with
/files/removed, so/files/image.pngreads the keyimage.png. A request that leaves the key empty returns400with the body{"error": "Object key required"}. - A request with no token returns
401, carries the headerWWW-Authenticate: Bearer, and names both accepted places in itsmessagefield. - A token the secret does not verify returns
401with the body{"error": "Invalid token"}and the messagejoseraised. - A verified token reads the object and returns it with the content type Object Storage stored, or
application/octet-streamwhen the object carries none.Cache-Control: private, max-age=3600keeps the response out of a shared cache. - A key that is not in the bucket returns
404with the body{"error": "Object not found"}. Every other storage failure returns500. console.errorwritesJWT verification failed:andStorage error:, and both lines reach the function logs.
The handler reads JWT_SECRET and BUCKET_NAME with Azion.env.get, so neither value is written into the code.
Configure the environment variables
Create a .env file in the root of the project, with the bucket the handler reads and the secret it verifies against:
Configure local storage for development
Azion Runtime answers a local Object Storage call from a folder on your machine. Add a storage block to azion.config so storage.get resolves while you develop:
Replace your-bucket-name, your-bucket-prefix, and ./path/to/storage/files with the values of your project. workloadsAccess is the camelCase spelling the configuration file uses for the bucket’s access level, and read_only is enough for a handler that only reads.
Deploy the function
The deploy sends the code, and azion sync sends the values the code reads. To put both on your account:
The Azion CLI builds the project and sends the function to your account.
The handler reads BUCKET_NAME and JWT_SECRET at run time. Without this step the variables exist only in your .env file, and the function does not find them.
The function runs on your account with the bucket name and the signing secret it needs.
Verify the setup
Sign a token with the same secret, then request an object three times: with the header, with the cookie, and with neither. To check the function:
Save the script as sign-token.mjs in the project folder, so Node.js reads it as a module, and run it with node sign-token.mjs:
The script prints the signed token. Set secret to the value you stored in JWT_SECRET, or the function refuses every token the script issues.
The response carries the object and the content type Object Storage stored for it.
The response is the same, which confirms the cookie fallback.
The function refuses the request with HTTP 401:
A signed token returns the object, and an absent or unverifiable token returns 401 without reaching the bucket.