4

I have a React app created with create-react-app which I have deployed to an Azure Web App. There is no back-end: the site is purely 'static'.

In the production environment, there are a number of keys to API services and other secrets that I need to keep secure, but which the client app needs to be able to read.

In React there's a mechanism for accessing environment-specific information using .env files, such as .env.production, but this is not suitable for keeping secrets, as environment variables mentioned in the code are substituted with the actual value from the .env file during the build process, and are consequently visible to anyone looking at the JavaScript in their browser.

Setting the values of the AppSettings can be done on the Azure Portal (or via suitable scripting in the CI/CD pipeline), but how can I read the AppSettings values at runtime?

A number of StackOverflow questions have been asked about this, but none of the answers address the fundamental question properly, or seem to miss the point. For example, here and here.

grahama
  • 100
  • 1
  • 9
  • 2
    About the point on secrets, wouldn't they be visible to the client no matter how the React front-end uses them? Since you know, it's front-end code so you should not have secrets to begin with. App settings are exposed as environment variables, so you need back-end code to access them. Or you need the env file approach. – juunas Aug 23 '18 at 16:04
  • @juunas, I know what you mean, but the .env file approach is effectively embedding the values in the code, so is there for all to see. I'm really asking how you'd get an API key (for example if you were using a third-party postcode lookup service, or a mapping service, which required an access key to use their API)? – grahama Aug 23 '18 at 16:54
  • 1
    You can't call an API like that from a front-end, unless you are okay making your API key public. No matter how you get the key, the HTTP call along with the headers will be visible to the user (they only need to open F12 tools). – juunas Aug 23 '18 at 16:56
  • 1
    When calling APIs from a front-end, a user-specific cookie or token is usually used. Then even if they grab it they can't do anything with it which they couldn't do otherwise. – juunas Aug 23 '18 at 16:57
  • If you need to call an API with an app-level API key, you need a back-end. – juunas Aug 23 '18 at 16:57
  • @juunas. If you're suggesting I wrap the calls to an API service in a backend process which I have to call, and then pass the results back to the client, that's not very efficient - that would result in two HTTP requests instead of one, not to mention adding a lot of code to the backend, especially if the API had multiple calls I wanted to use. Does anyone have any suggestions to answer the actual question I asked? – grahama Aug 24 '18 at 13:53

1 Answers1

1

First off, this topic is puzzling lot of developers around the globe and it should be addressed properly in Aspnet Core. One viable option would be to set up Server-Side Rendering, but there are some of us who wouldn't benefit from it. Also, there are no proper examples for doing it for ReactJS + Redux in Aspnet Core world.

My solution is to go for a InMemoryFileProvider, see https://github.com/dazinator/Dazinator.AspNet.Extensions.FileProviders for details about the NuGet package.

To start using one, in my Configure() I'll do a:

configuredSpaFileProvider = CreateFileProvider(env);
app.UseSpaStaticFiles(new StaticFileOptions
{
    FileProvider = configuredSpaFileProvider
});

My CreateFileProvider() contains an instantiation of InMemoryFileProvider-class, which can be populated with additional fake "files". Pre-existing static file on filesystem will be served with a PhysicalFileProvider.

To allow access to index.html, CSS, JavaScript and all possible static files, do what a DefaultSpaStaticFileProvider would do:

var defaultPath = "build";
if (env.IsDevelopment())
    defaultPath = "public";
var absoluteRootPath = System.IO.Path.Combine(
    env.ContentRootPath,
    $"{ReactJSdirectory}/{defaultPath}");
var physicalFileProvider = new PhysicalFileProvider(absoluteRootPath);

Then create a provider and virtual file like this:

var inMemoryProvider = new InMemoryFileProvider();
inMemoryProvider.Directory.AddFile("/", new StringFileInfo(configJsContent, "app.config.js"));

Finally glue them together, prioritizing on the virtual files:

return new CompositeFileProvider(inMemoryProvider, physicalFileProvider);

Now that a dynamic JavaScript-file exists, it needs to be accessed on client-code. There are options for this, but I'll simply do a:

<script src="/app.config.js" async defer></script>

in my index.html. The trick is to construct a string with suitable JavaScript-parseable content setting up variables and storing them into window-object and accessing them later on a client.

Now my 12 factor application is fully compliant with third factor "Store config in the environment".

Jari Turkia
  • 1,184
  • 1
  • 21
  • 37
  • 1
    I guess it goes without saying that anyone with a browser could download your `/app.config.js` and see what's inside it, so you shouldn't store any secrets in there. – Matt Frear Apr 12 '21 at 04:18
  • Yes. Any secret-handling on the client-side is strongly discouraged. Such information won't stay secret very long. – Jari Turkia Apr 12 '21 at 08:51
  • Ideally since the app service configuration gets transformed into environment variables, the React app would be able to just read those environment variables, but that's not currently possible. https://create-react-app.dev/docs/adding-custom-environment-variables/ The environment variables are embedded during the build time. Since Create React App produces a static HTML/CSS/JS bundle, it can’t possibly read them at runtime. To read them at runtime, you would need to load HTML into memory on the server and replace placeholders in runtime. – Matt Frear Apr 13 '21 at 05:17
  • Well, that's a good idea! Why didn't I think of that. :) – Jari Turkia Apr 14 '21 at 06:54