'How do I pass the environment info to javascript code with importmaps

I’m using Rails 7 with importmaps and I’m trying to make JavaScript code conditionally depend on the environment specific config. At the very least, I’d need to pass what environment is it in the first place (dev, test, etc) and ideally I’d like to pass arbitrary env dependent configuration.

What I could do is serve a js file from a controller and create a global config var in it, serving whatever config info I want to be available to the js code.

But I’m wondering if I’m missing some obvious “official” way do so. Is there one?



Solution 1:[1]

Thought I'd post an architectural answer on how I've managed this in the past, to provide an easy to manage and secure solution for all types of user application.

It is technology agnostic but this design should work well for your Rails app. I would avoid Rails import maps if they didn't allow you to follow these principles.

BUILD BINARIES ONCE

There are quite a few advantages to building code once, whatever the technology, then promoting assets down a pipeline without changes. So you test exactly the same code everywhere. If there are errors, stack traces are identical and so on:

  • DEV --> STAGING --> PRODUCTION

WEB BINARIES

These days I like to build Javascript into bundles, then add subresource integrity tags to the index.html file, to tie resources together, as in this demo app of mine:

<!DOCTYPE html>
<html lang='en'>
    <head>
        <meta charset='utf-8'>
        <meta name='viewport' content='width=device-width, initial-scale=1, shrink-to-fit=no'>

        <base href='/spa/' />
        <title>OAuth Demo App</title>

        <link rel='stylesheet' href='bootstrap.min.css?t=1649189065161' integrity='sha256-YvdLHPgkqJ8DVUxjjnGVlMMJtNimJ6dYkowFFvp4kKs='>
        <link rel='stylesheet' href='app.css?t=1649189065161' integrity='sha256-B7pu+gcFspulW4zXfgczVtPcEuZ81tZRFYeRciEzWro='>
    <body>
        <div id='root' class='container'></div>

        <script type='module' src='vendor.bundle.js?t=1649189065161' integrity='sha256-p+HJCny/HKNJXb18K7GW6jKqw4bZN2AZnUWtXJCDLT8='></script>
        <script type='module' src='app.bundle.js?t=1649189065161' integrity='sha256-WGayM6x6jt2rJ5PwBvuV+vytuDBdBOkHQoO08iCdWfM='></script>
    </body>
</html>

CODE POINTS TO PRODUCTION

In code I have an environment object for the production configuration, as in the below example. This only contains public / non sensitive data and I wouldn't care if a hacker got hold of it.

export const productionConfiguration = {

    app: {
        webOrigin: 'https://web.authsamples.com',
        apiBaseUrl: 'https://tokenhandler.authsamples.com/api'
    },
    oauth: {
        oauthAgentBaseUrl: 'https://tokenhandler.authsamples.com/oauth-agent',
    }
} as Configuration;

OTHER ENVIRONMENT OVERRIDES

At runtime, if running in a different origin, I download a static config.json file from the web origin, containing the environment data:

public async get(): Promise<Configuration> {

    if (location.origin.toLowerCase() === productionConfiguration.app.webOrigin.toLowerCase()) {
        return productionConfiguration;
    }

    return this._download();
}

This enables new test environments to be created without needing to rebuild code. I like to avoid this download for production users, so that all of the main web resources are tied to the index.html file.

MOBILE

The same principle of shipping with production configuration built into APK / IPA files can be used in mobile apps. Mobile environment data might include different info, as in this example app of mine:

{
  "app": {
    "apiBaseUrl":             "https://api.authsamples.com/api"
  },
  "oauth": {
    "authority":              "https://cognito-idp.eu-west-2.amazonaws.com/eu-west-2_qqJgVeuTn",
    "clientId":               "3pj7bd0ff8h1klhok774303dq8",
    "webBaseUrl":             "https://authsamples.com",
    "loginRedirectPath":      "/apps/basicmobileapp/postlogin.html",
    "postLogoutRedirectPath": "/apps/basicmobileapp/postlogout.html",
    "scope":                  "openid profile email https://api.authsamples.com/api/transactions_read",
    "deepLinkBaseUrl":        "https://mobile.authsamples.com",
    "customLogoutEndpoint":   "https://login.authsamples.com/logout"
  }
}

The usual technique is then to have a settings page where these values can be overridden, eg to point the app to test environments.

SECURE ENVIRONMENT DATA

Of course the UI only downloads any sensitive environment data after the user has authenticated, eg by sending a secure cookie or access token to an API.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1