How to use Expo's environment secrets with `expo publish`

After reading the article Environment variables and secrets - Expo Documentation I’ve created a single environment variable to toggle the configurations. But expo publish will never retrieve the secrets from Expo and have to manually add them to the .env file. I was wondering if there is a better way of publishing updates with Expo secrets?

For more context I included my app.config.js:

import 'dotenv/config'

/**
 * For development the variables are set within the .env file. Retrieve the credentials from LastPass.
 * For external and production builds the variables are used stated in https://expo.dev/accounts/<company>/projects/<project>/secrets.
 */

const variables = {
  API_URL: process.env.API_URL,
  API_KEY: process.env.API_KEY,
  DEBUG: true,
  // And more variables are set here..
}

if (process.env.APP_ENV === 'external') {
  variables.DEBUG = false
  // And more variables are set here..
}

if (process.env.APP_ENV === 'production') {
  variables.API_URL = process.env.PROD_API_URL
  variables.API_KEY = process.env.PROD_API_KEY
  variables.DEBUG = false
  // And more variables are set here..
}

const isPublishing = process.env.ACTION === 'publish'

export default ({ config }) => {
  if (
    isPublishing &&
    Object.values(variables).some((variable) => !variable)
  ) {
    throw new Error(
      'Some variables are missing. Please check your local .env file.'
    )
  }

  return {
    ...config,
    extra: {
      ...variables
    }
  }
}

You can keep them in .env file and use them both for building and publishing.

It’s fine to commit them to the repository. If you are passing those values to your js code then they are not secrets, anyone with the application installed can access them relatively easily by accessing js bundle embedded in the application, so you are not adding any more risk by keeping them inside the repo.

If you have multiple environments you can have multiple .env files and select which to load based on some other env variable. You will still need to specify env manually when publishing, but it would be just one value.

Thank you for your answer! Does this mean we don’t need the Expo environment secrets and entirely use .env files and change them based on the environment instead?

yes, a secret mechanism is intended for values that never end up as a part of an application e.g. sentry key to upload source maps, npm token to access private repo, token to send message to slack …

Thank you for answering so quick!

Just to be sure: Getting environment variables by .env file(s) and read them in app.config.js with process.env.* won’t expose the variables during build and publish step? We are using some credentials that should remain secret, such as API key and (Amazon) Amplify configurations.

Hope to be hearing from you!

won’t expose the variables during build and publish step?

You will need to commit .env file in the repo so yes, It will expose them, that is the whole point, if you are passing any values in app.config.js to your javascript code then it’s embedded in your bundle, it does not matter if you pass them via secrets or just commit .env file in repo. There is no such thing as securely storing secrets in client-side applications regardless of what technology you are using.

If you have values that you don’t want to expose then you should use eas secrets to pass them to the worker, but you should not need any of those secrets when you are publishing an update.

I am sorry, still a bit confused about this. Can you verify that when I build an app the js bundle will not contain the raw values but only “process.env.SECRET_VARIABLE”?.

If they are visible, it feels like it doesn’t matter if I use the .env file, values directly or Expo secrets. They will always be visible in the JS bundle that people can easily get access to (as you mentioned earlier).

I have the following set-up now:

.env:

SCHEMA_PATH=<secret>
API_KEY=<secret>
... and more settings

app.config.js:

const envPath = process.env.APP_ENV === 'production' ? '.env.production' : '.env'
require('dotenv').config({ path: envPath })

export default ({ config }) => {
  return {
    ...config,
    extra: {
      SCHEMA_PATH: process.env.SCHEMA_PATH,
      API_KEY: process.env.API_KEY,
    }
  }
}

package.json (not complete):

"build:production": "APP_ENV=production eas build --platform ios --profile production",
"publish:production": "APP_ENV=production expo publish --release-channel production"

eas.json:

{
  "build": {
    "external": {
      "releaseChannel": "external",
      "env": {
        "APP_ENV": "external"
      }
    },
    "production": {
      "releaseChannel": "production",
      "env": {
        "APP_ENV": "production"
      }
    }
  }
}

Can you verify that when I build an app the js bundle will not contain the raw values but only “process.env.SECRET_VARIABLE”?

It will contain raw values, actual values of API_KEY and SCHEMA_PATH from your example will be embedded in js bundle.

If they are visible, it feels like it doesn’t matter if I use the .env file, values directly or Expo secrets. They will always be visible in the JS bundle that people can easily get access to (as you mentioned earlier).

yes, it’s not possible to securely store secrets in any client side applications

Thank you for your answer! I have gave it some more thought and it makes sense now. I am using the expo constants everywhere in my application which contains the raw values anyways, so it doesn’t matter which option I choose. :sweat_smile:

Thank you for your time!

This topic was automatically closed 20 days after the last reply. New replies are no longer allowed.