Scoping zsh variables

By default in zsh, if you define a local myVariable it will be available to the whole script running it.

Any of those variables defined in your .zshrc will also be visible in your terminal. This can create weird bugs when you accidentally defined a variable with the same name as another zsh script.

Note that those are not environment variables. Even if you can read them from your zsh terminal, they are not accessible from other tools. To explicitly make them available as environment variables, you need to export them.

If you define a variable inside a function, it stays scoped to that function, though. Also, anonymous functions are run as soon as they are defined, and discarded afterwards.

Using those two features, we can define our variables without them being available in the terminal global scope.

I find it a best practice to wrap any of my sourced zsh scripts in a function () { } block, like this:

local one=1
function () {
  local two=2
  export THREE=3
}

one is accessible in the whole .zsh script, including inside of the function, and even during your whole terminal session (you can echo $one). Other, non-zsh, scripts won't be able to read it though.

two is accessible only in the body of the function, and is scoped there and won't be accessible from outside. It's perfect for small variables you need to simplify your code but don't need laying around.

THREE is an environment variable, that can be used in the terminal (echo $THREE) as well as in any other script you're running from the terminal. It's useful if you need to set some global flags or editing global settings (like the $PATH variable).

Dynamic variable names in zsh

Imagine the following scenario:

local projects=(blog www meetups)
local color_project_blog=146
local color_project_www=75
local color_project_meetups=23

And now you'd like to iterate on all projects, and display their associated color. You need dynamic variables, where part of the variable name is itself coming from another variable.

This will be achieved using two zsh modifiers: ${(P)} and ${:-}.

Building a variable name using dynamic variables

local name=${real_name:-Alice} means: set the $name variable to the content of the $real_name variable. If $real_name is empty, use Alice as the default value.

Anything after the :- is used as the default value, and it can even interpolate variables. So if you also have local default_name="Alice", then you can have local name=${real_name:-Default name set to $default_name}.

That way, $name is equal to $real_name, unless $real_name is empty, in which case it's set to Default name is Alice.

One can even remove the first variable to force zsh to use the default value. So local name=${:-Default name is $default_name} is valid syntax and will set $name to Default name is Alice, allowing one to interpolate variable names when setting variables.

Reading such a dynamic variable

We now have a fancy way of building a string with variable interpolation inside a ${}.

To read it, it's a bit more complex as we need to use this (already barbaric-looking) syntax with another modifier.

local projects=(blog www meetups)
local color_project_blog=146
local color_project_www=75
local color_project_meetups=23

for project_name in $projects; do
  local project_color=${(P)${:-color_project_${project_name}}}
  echo "${project_name} color is ${project_color}"
done

Iterating on words and lines in zsh

When writing zsh scripts, I often needs to iterate on elements, but depending on how I create them, they can either be a core zsh array, a string of words, or the output of a command, delimited by newlines.

Iterating on an array

Iterating on arrays in zsh has a pretty straightforward syntax:

local projects=(firost aberlaas golgoth);
for project in $projects; do
   echo "${project} is one of my projects";
 done

Handling string of words

By default, zsh does not split a string in words like other shells (Bash) do, so iterating on words requires the ${=} syntax.

The ${=} notation triggers the Bash-compatible behavior by switching the SH_WORD_SPLIT zsh option for that variable.

Iterating on the words

local projects="firost aberlaas golgoth";
for project in ${=projects}; do
   echo "${project} is one of my projects";
 done

Accessing one element specifically

Note that if I want to convert the string of words into an array, to specifically access one of its elements, I need to wrap it in ().

local projects="firost aberlaas golgoth";
local projectsArray=(${=projects})
echo "$projectsArray[2] is my second project"

A note on words

Note that zsh will split by words, meaning that multiple spaces will be removed:

local projects="   firost        aberlaas            golgoth    ";
local projectsArray=(${=projects})
echo "$projectsArray[2] is still my second project"

Iterating on lines in a string

New lines are considered words separators, so technically the ${=} could be used to split a string by newlines. But this will also split by spaces, so if your lines contain spaces, you final array will not be what you expect.

To split only by new lines and not by space, you need to use the ${(f)} syntax. This proves useful when parsing long output from other commands

Iterating on the lines

local projects="$(ls)";
for project in ${(f)projects}; do
   echo "${project} is a project file in my dir";
done

Accessing one line specifically

local projects="$(ls)";
local projectsArray=(${(f)projects})
echo "$projectsArray[2] is my second project"

Firebase authentication with Auth0

I use Auth0 on my current project to authenticate my users. It prompts my users with a clear modal UI to ask them to authenticate using Google/GitHub/Other third parties. It's an easy way to handle authentication from the front-end without too much hassle.

But my app also uses Firebase as its main database, and I'm querying it from the front-end. I've set my Database rules to auth != null meaning that only authenticated users can read or write my data.

The Firebase JavaScript SDK provides widgets to handle authentication with a GUI directly with Firebase, but as I'm already using Auth0 for the rest of the app, I don't want to have to ask my users to authenticate twice.

Auth0 also do not provide any integration with Firebase out of the box. It used to provide one in the past, but it seems to have been deprecated. It means that I have to build my own plumbing between Auth0 and Firebase.

Because I already had to handle Firebase authentication in another part of the app, I was already familiar with the fact that I needed a custom token. All I needed to add was a way for me to request it from the front-end.

What I did was create a Firebase function publicly available on a specific url that will return a custom token. I will then query this url from my front-end to get the token and then authenticate to Firebase using it.

But such a naive implementation will expose my custom token through a public endpoint that anyone could request. I had to secure it a bit more.

What I did was to get the access_token obtained from auth0 during authentication and send it to my Firebase function in its payload.

The Firebase function would then call auth0 with the access_token to get information about the user associated with this token. If it succeeded (and the user email was matching the one initially sent), I could go forward and return the newly mint token. This token will then be used by the front-end to authenticate to Firebase.

In the end, here is the overview of the complete token dance:

  • Authentication to Auth0, saving access_token locally
  • Calling the Firebase function with this access_token
    • In turns calls Auth0 again with this access_token to valide it
    • It matches, so it returns a Firebase access token
  • Call Firebase to authenticate using the access token I got from the Firebase function

Authentication to Firebase Database from a Firebase Function

On one of the projects I'm working on, I'm using both Firebase functions and Firebase Database. I'm calling functions in reaction to specific events, that will save data in my Firebase Database.

I managed to have something running in development in a week. As I was still developing, I kept the default ACL to read:true and write:true on the database, meaning that anyone could read and write my data.

When time came to put to production, I followed the security best practices of Firebase and set the rules to auth != null meaning that only authenticated users could read or write data. I thought that "authenticated users" would mean anyone with the API key.

Turns out it's not what it means. Identification (through an API key) and Authentication (through a login/password) are two different things. And Firebase is expecting me to authenticate before being able to access my data, even if I'm calling the db from a Firebase function.

Most Firebase documentation will explain how to authenticate with a front-end application. The SDKs even provides GUI elements to make the integration smoother. It has widgets to incorporate authentication with third parties such as Twitter, GitHub or Google.

Authentication using a custom token

That's useful; except when you're running your app from the backend and can't use those GUI elements. To authenticate from the backend, I had to use another method: authenticating through a custom token.

Basically what it means is that I'll give to the Firebase authentication method a token that could only have been crafted by someone with admin access. I needed to create my own token to then use it to authenticate.

What was a bit strange to grasp at first was that I needed to instanciate both a firebaseAdmin (to create the token) and a regular firebase instance to actually authenticate using this token.

Getting the custom token

The first step is to have an instance of firebaseAdmin running. I found all the information in the official Firebase documentation. It needed to be initialized with the credential option set the a valid certificate generated from my serviceAccountKey.json key. This part is crucial as it will allow my firebaseAdmin to mint (create) new tokens.

import * as firebaseAdmin from 'firebase-admin';
firebaseAdmin.initializeApp({
  credential: firebaseAdmin.credential.cert(serviceAccountConfig),
  databaseURL: 'your_url'
});
let customToken = firebaseAdmin.auth().createCustomToken('backend');

The 'backend' value can actually be any string, but will identify the user of this token (you can see it in your Firebase dashboard).

Authenticating using the custom token

Now that I had the token, I had to actually authenticate using it. To do so, I initialized my firebase with initializeApp as usual, then sign in with the custom token:

import firebase from 'firebase';
firebase.initializeApp({...your_config});
firebase.auth().signInWithCustomToken(customToken);

Now my firebase instance can read and write data from my Firebase database.

Conclusion

Hope that helps. I got confused at first between identification and authentification and it also took me a while to understand that I needed to mint the custom token with the admin and the authenticate using it.