Secrets

In this tutorial you will learn how to use secrets in Toit projects.

Introduction

Many online services require a secret to be able to access them. For example, a database might require a password, or a service might require an API key. For open-source projects (and often for closed-source ones too) these secrets should not be stored in the source code.

In the remainder of this tutorial we will show a simple way of storing secrets in Toit projects without ending up storing them in the version control system.

Prerequisites

We assume that you have set up your development environment as described in the IDE tutorial.

We also assume that you are familiar with running Toit programs. If not, have a look at the Hello world tutorial.

Note that you can do this tutorial without a device. In that case, you need to use the -d host option whenever you invoke jag run. The program will then run on your computer instead of on a device.

The project

We will assume an API key "123456" that we want to use in our program.

A simple program client.toit that uses this API key could look like this:

API-KEY ::= "123456"

main:
  print "Using the API-KEY now: $API-KEY"

To simplify things we simply "use" the API key by printing it.

Storing the secret for devices

A common way of passing secrets to programs is to use environment. However, when flashing a program to a device, the environment is not available.

Instead, we can store the secret in a separate file that isn't checked in.

A secrets file

The most straight-forward way of storing the secret is to create a file called secrets.toit with the following content:

API-KEY ::= "123456"

The original client.toit program can then be changed to the following:

import .secrets

main:
  secret-api-key := API-KEY  // Imported from secrets.
  print "Using the API-KEY now: $secret-api-key"

The secrets.toit file should not be checked into version control.

One problem with this approach is that there isn't a good way to distinguish between secrets for different environments. For example, if we have a development environment and a production environment, we might want to use different secrets in the two environments.

The next section shows a way to solve this problem.

Using a separate entry file

Whereas the previous approach imported the secrets file directly, we can instead create a separate entry file that passes the secret to the main program.

Change the original program to the following:

main api-key:
  print "Using the API-KEY now: $api-key"

Then create a file called client-production.toit with the following content:

import .client as client

API-KEY ::= "123456"

main:
  client.main API-KEY

The main function in client-production.toit simply calls the main function in client.toit with the API key as an argument. The client-production.toit file should not be checked into version control.

This approach makes it easy to have different secrets for different environments. For example, we can create a client-dev.toit file with different secrets for the development environment.

Since there is now more boilerplate code, we should create a template for the client-X.toit files. For example, a client-template.toit file could look like this:

import .client as client

API-KEY ::= "<API-KEY>"

main:
  client.main API-KEY

Whenever we want to create a new client-X.toit file, we can simply copy the client-template.toit file and replace <API-KEY> with the actual API key.

At this point, we should add the client-template.toit file to version control, and then ignore the client-X.toit files. This can be done by adding the following line to the .gitignore file:

client-*.toit

Using the environment

When running a program on the desktop, we can use the environment to pass secrets to the program. In that case we need the host package. Install it by running the following command:

jag pkg install host@v1

See the package tutorial for more information.

The host package provides the os library which gives access to the environment.

We can add a few more lines to the original client.toit program to use the environment:

import host.os

main:
  api-key := os.env.get "API_KEY"
  if not api-key or api-key == "":
    print "Please set the API_KEY environment variable."
    return

  main api-key

main api-key:
  print "Using the API-KEY now: $api-key"

Since we still have a main function that takes an api-key argument, we can still use the client-production.toit file to run the program on a device. However, we can now also run the program on the desktop by setting the API_KEY environment variable.

Conclusion

In this tutorial we have seen how to use secrets in Toit projects. We have seen how to store secrets in a separate file, and how to use the environment to pass secrets to programs.