In this article I will share what I could not find (exactly) in the documentation and what my AI assistants wildly hallucinated about (too little documentation, too little code samples?). It is a fairly simple mechanism that I am planning on using a lot: save data from a static web application in a secure way without having to run a database or writing a lot of code.
This is what I want to achieve in some detail:
My static web application is started from GitHub Pages. It then has the user authenticate (against my company’s EntraId tenant); using the acquired JWT token, OCI API Gateway is called to retrieve the (confidential) data for the application that only my colleagues can see. The data is in a JSON file, stored on OCI Object Storage and accessible over a Pre Authenticated Request URL included in the Deployment definition on API Gateway. This is described in an earlier article.
Then, the user can make changes to their own data in the application, and save those changes. This happens by making another call to the API Gateway. This PUT call is also checked for the authentication token (as well as CORS policies). Then, URL for the backend to be invoked is constructed from another Pre Authenticated Request URL (one that allows Listing and Writing objects) and that includes “deltas/” as a prefix in its definition, so only objects in the “deltas folder” can be seen and written) and the user claim in the JWT token. The API Gateway makes sure that the name of the file corresponds with the name of the user in the token — even if the user has tried to manipulate the data submitted and make it about some other user. When we process the user specific files on the backend, any file where name of the file does not correspond to the contents of the file will be discarded.
This flow in more detail is illustrated by this image:
Implementation Steps
Create the Pre Authenticated Request (URL) for the bucket — with prefix set (to restrict access to “folder” conclusion-assets/deltas/) and Read, Write and Listing enabled for all objects.
Assuming a Deployment on API Gateway with Authentication and CORS configured (as described in this article), make sure that the CORS settings include the OPTIONS and PUT methods in the Allowed methods and the Content-Type in addition to Authentication in the Allowed headers:
Define a Route that associates the public endpoint with the dynamic URL that leverages the Pre Authenticated Request and defines the file name using the user claim from the JWT Token.
The URL is defined from the combination of the PAR, the prefix and the expression ${request.auth[user]} that evaluates to the value of the user claim in [the IdToken in] the JWT Token.
When I save my own data from the web application, the resulting JSON file is created as “conclusion-assets/deltas/Lucas Jellema”. Nothing I can do from the web application to prevent that — because the name is derived from the authentication token that I cannot tamper with. And nor can my colleagues.
The logging produced by API Gateway explains what happened:
In words:
"Dynamic request url formed:
[https://hanz.objectstorage.us-ashburn-1.oci.customer-oci.com/p/1EJ9/n/hanz/b/laptop-extension-drive/o/conclusion-assets/deltas/Lucas Jellema]
from configured url:
[https://hanz.objectstorage.us-ashburn-1.oci.customer-oci.com/p/1EJ9/n/hanz/b/laptop-extension-drive/o/conclusion-assets/deltas/${request.auth[name]}]"
I will now create a script or function that will periodically process all files in the deltas folder. Any file where the data in the file (the name property first and foremost) corresponds with the name of the file will be processed (and the data added to main data file that all users can make use of).
The code that makes the call from my web application:
const deltaEndpoint = "https://odzn.apigateway.eu-amsterdam-1.oci.customer-oci.com/conclusion-proxy/speakerpool-delta";
try {
const response = await fetch(deltaEndpoint, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(updatedProfileData)
});
if (!response.ok) {
...
}
and the logging in Console and Network pane:
Lessons learned along the way
Some of the things I learned along the way:
- prefix $ in Terraform configuration files with another $ — to prevent untimely evaluation a Terraform variable (so $${request.auth[user]})
- the JSON document you get by exporting an API Gateway Deployment using OCI CLI is not correct! (properties are named with xxx-yyy instead of xxxYyy , you need to convert all hyphenated keys to camelCase keys). Here is the command.
oci api-gateway deployment get \
--deployment-id ocid1.apigatewaydeployment.oc1..xxxx \
--query "data.specification" \
--raw-output > deployment-spec.json
- expressions such as ${request.auth[user]} cannot be followed by “.json”. This is not correct syntax: “${request.auth[user]}.json”, to be used in the URL. The feedback about this is somewhat unclear from both Terraform apply and the console (Invalid specification.routes[1].backend.url: contains invalid path segment for HttpBackend). The documentation does not mention this at all.
Note: multiple expressions can be strung together. It is perfectly fine to have this definition for URL:
https://hanz.objectstorage.us-ashburn-1.oci.customer-oci.com/p/1EJ9/n/2hanz/b/laptop-extension-drive/o/conclusion-assets/deltas/${request.auth[oid]}${request.auth[preferred_username]}${request.auth[name]}
- beware the hallucinations from AI tools. I got all kinds of recommendations, including syntax that does not exist at all. All kinds of rabbit holes to fall into and chase wild and meaningless options.
Of course I should have figured out much sooner that this was nonsense:
Conclusion
The solution I have created is satisfactory. With only configuration and no special moving parts I can have users save data in a proper, authenticated way.
I would have preferred to add the .json extension to the filename, but I do not know how to do it (other than by creating a request header with the value of json and adding it to the URL definition.
The documentation on using the ${request.auth[claim]} is somewhat underwhelming; no complete examples. And the community seems to have produced little of value in this area either. Which leads to poor AI tool assistance. I have said it before: technology that does not have a lot of resources (StackOverflow, documentation, code samples, tutorials, community articles) and therefore does not get great AI support is under threat. Why use stuff that my tools cannot properly help me with?