Designing unique game id's and join codes
by Jesus Moreno, USFWS

Designing unique game id's and join codes


Séance
databases supabase postgres

This solution was part of the Séance app I created. For Séance, I used the Supabase BaaS platform to provide database and realtime features, backed by Postgres. All you need to know about Séance for this post is that, in Séance, users can either create a new game, or join an existing one using a game identifier given to them by another user.

In the early stages of the app, the player would join a game by entering the game’s primary key id from the database. This worked, but it isn’t really what a primary key is for, and the sequential nature meant a troll could discover id’s that were likely to be in use and disrupt somebody’s game.

The clearest solution for the sequentialness problem was to change the type of the id column to a uuid, provided by the uuid-ossp extension that Supabase makes easily available. I used the function uuid_generate_v4 (also provided by uuid-ossp) to generate non-sequential random values that look like: a8c7fbce-5e0c-4691-96e6-0bc75164e67d

Obviously, these uuid’s are difficult for a user to enter to join a game (and I was trying to get away from using primary keys for that anyway), so I added a join_code column. To get user-friendly, non-sequential, close-enough-to-random join codes took a few steps:

  1. I created a Postgres sequence called seance_id_sequence (not the best name in hindsight). I used all defaults, but set it to cycle in case it ever runs out (it won’t on this app though).
  2. I enabled the pg_hashids extension to make its id_encode function available.
  3. I made a function called generate_join_code, defined as:
begin
    new.join_code := id_encode(nextval('seance_id_sequence'), 'salt', 6, '0123456789abcdef');
    return new;
end

This will create a (usually) 6-character code, seeded with the next number in the sequence, using only numbers and lowercase letters ‘a’ through ‘f’.

  1. I made a trigger to assign the value of generate_join_code() to seance.join_code whenever a new séance is created, on the BEFORE INSERT event.

With that, all the above problems were solved. Séance id’s are no longer guessable, are no longer used to join games, and are no longer presented to users in the UI. Instead, players can join games with join codes, which are concise, human-friendly, and unguessable.