Session-based invite codes for users without sign-in

Hi there,

I’m trying to implement an invite-only survey without requiring users to log in. I have an Invites table and a mechanism for randomly auto-generating new invite codes and inserting them as new rows within that table. So far so good.

I want the first screen of the app to ask the user for an invite code, and if it’s valid, to proceed with the rest of the multi-page survey. I have the submit button hooked up to an action which validates the invite code. I’ve implemented this validation using a relation to the invites table, and then a conditional column which computes the validity (“valid” if the relation is non-empty, and “invalid” if it’s empty). This works fine when logged in, but the problems come flooding in when not logged in.

To ensure that all the responses from one user to questions in the survey are associated with a single invite code, I need some way to persist that invite code across the whole user session. The problem is that apparently there’s no native support for passing parameters between pages in Glide, nor is there native support for proper session variables, e.g.
Session Variable Column.

I simply cannot get this working despite having tried SO MANY combinations of things, including:

  • Using the users table as a pseudo-store for session variables (only works when logged in, as it seems every user not logged in shares the same data in near real time)
  • Row owners on the users table to solve the above sharing problem
  • User-specific column in the users table for the invite code
  • The first page being a form which creates a new entry in a guests table
  • User-specific column in a guests table for the invite code

In many cases when not logged in and/or using user-specific columns, it seems that computed fields are simply inaccessible or not updated.

I found the documentation on USCs confusing (e.g. how is a particular USC value tied to a particular user? and how does that work when not logged in)? I found several posts in this forum which gaves hints about it, but those were all related to different use cases to mine. I also had a similar problem with how row owners is supposed work with users not logged in.

Would massively appreciate help here because I’ve hit multiple brick walls!

When a user is signed in, the value that the user writes to a user specific column will be saved in connection with that user.

It will only persist until the app is closed.

I think this is your best bet. Don’t use the user table or user profiles if users are not signed in. Also don’t use Row Owners. User profiles and row owners only make sense for users that are signed into your app. Instead create a single row table that will be the source of your main tab when the app loads. Add a user specific column to that table which will hold the code entered by the user. If you need that entered code in other tables, use a Single Value column to bring it into the other tables.

3 Likes

I’ve tried this but I’m hitting one of the same problems I saw before. I have a Relation column to check whether the invite code in the USC column is valid:

and then I have a Submit button linked to an action which checks whether this Relation column is non-empty, and allows the guest to proceed if so:

However that Submit button is not appearing at all (neither in the Layout editor nor in the published app running from an incognito browser window), despite having no Visibility setting. If I changed the button’s action to something like showing a notification message, the button appears. So there seems to be a problem with using a Relation column in a conditional inside an action. I’m not sure why - perhaps it’s related to the fact that the Relation column is based on a USC column from a user who hasn’t signed in?

Hrm, actually I didn’t do it as a form which adds a row yet - just a text entry field, although it seems to be populating the Guests table just fine despite that:

Also I have concerns about the security of this. Don’t I need row owners to be set on the Invites table containing all the invite codes? This old post below seems to suggest that without Row Owners enabled, all the invite codes will be downloaded to the end user’s device before the check is done? Or are you saying that I only need the Guests table to not use Row Owners, but it’s OK for the Invites table to use it?

Essentially I need the Invites table to be unreadable to users who aren’t logged in, whilst still being able to cross-check the validity of a particular invite code provided by a guest against the list of valid codes. This means the check needs to be done server-side, but I’m not sure if that can happen when using USC values?

I tried adding a form which submits a new row to the Guests table, linked to the same action, but this doesn’t work either. In fact, after clicking Submit the action doesn’t even get triggered:

I don’t think there is a problem with the relation. Even the fact that it’s using a USC column shouldn’t be a problem. I’d be more inclined to think it’s a problem with the tab you are trying to go to. Is the tab you are going to still active? How are you hiding the tab from the user?

For your use case, you would not want to use a form. Using the detail screen is fine.

As you should. The only guaranteed way to enforce data security is to require users to sign in and apply row owners. Otherwise in your case, someone with enough knowledge could snoop the data that is downloaded to the device and find the list of invite codes, or even snoop the survey results.

You can make it harder by enabling unused invite codes for a short period of time and deactivate then once used, which would make it harder for someone to do anything with them, but it’s not foolproof.

Plus there is still the fact that all survey results could be floating out there too. You could change your process to have all your survey answers written to user specific columns, and then have a final submit button that writes all of the answers to another table with row owners applied and the row owner email set to an admin email. Users would not have access to any submitted survey results because of row owners, but could still submit the survey without signing in.

2 Likes

Ahah! You are right, the destination tab was only visible for logged in users which specified a valid invite code, because that’s the only way I could get it working previously.

However, I’m trying to change it to depend on whether the single visible row in the Guests table has a valid invite code, but it only lets me create tab visibility conditions based on the User Profile, which is of course blank because the guest isn’t signed in:

Why won’t it let me set a condition based on the data in the table which is the data source for the tab?

The invite codes column is currently a computed value. Sniffing the network traffic in the browser console, I see the non-computed values included in one of the responses, but I don’t see computed values. Are these values computed server-side or client-side, and similarly where is the matching of relation fields done? If both are done server-side, maybe I could generate invite codes from, say, the rowId and a secret key which is only accessible server-side?

Right :frowning:

That’s an interesting idea. But if I can’t secure the invite codes properly then it’s already questionable whether this approach is viable.

Thanks a lot for the great help so far!

User profiles are the only way to condition tabs. I would probably just hide the tab from the nav bar instead, but it won’t prevent people from directly going to the url.

Most computed columns are client side… including relations. You could debug the code and eventually see the computed values.

1 Like

I already hid everything from the nav bar, but yeah I figured that still doesn’t stop browser navigation.

I don’t understand why tab visibility can only depend on the user profile, but component visibility can be conditioned on other data sources. This seems like a weirdly artificial limitation. It occurred to me that I could at least limit visibility of all the components, but that’s really tedious and still leaves users open to viewing blank pages.

Shame to hear that most stuff is computed client-side. And I think I read elsewhere on these forums that when there’s a filter on the tab’s data source, even the filtering happens client-side?

So even making all the components invisible probably doesn’t stop sensitive data being downloaded client-side.

I don’t have a good answer. Even though all tabs need to be linked to a source table, I suppose for various reasons, choosing a specific row from that source table is not always a good practice for determining if a tab should be shown or not. Maybe there’s other technical reasons behind it too. I don’t know. On the other hand, the user profile is globally accessible, so it makes more sense to use a global record to control some of these things.

I’ve always been an advocate of having a non-user based global table, or multiple global tables with maybe a restriction that they can only have one row. Not sure if that will ever happen though.

@Eric_Penn could probably chime in with more details, but to my understanding, I think if you add a Single Value column to the user table to retrieve a value from another table, you can still use it globally…even if not signed in. I’ve shyed away from doing it because I don’t fully understand how it works if a non signed in user does not have a user row, but apparently it works, so that’s something to try.

Well, your apps would be a lot slower and less responsive if traffic for computed columns had to travel back and forth from the server. Glide apps are fast because a lot of stuff happens client side.

Correct. Filters and Visibility happen based on data that’s already been downloaded to the user device. That’s why filters and visibility are not as secure as using row owners, which happens server side.

2 Likes

On further thought, I don’t see how this invite codes system can possibly be implemented securely, even with users logging in. If the only way to keep invite codes properly secret is to have row owners enabled on that table, then when a user has logged in and enters an invite code, how can the app decide whether it’s valid if the lookup is only done client-side?

If an invite code is properly attached to a specific user’s email address when they are assigned an invite code, and if the table with the codes is under row ownership, and the user is signing in, then it’s completely secure because a user will only be able to use the invite code that’s been assigned to them. Meaning…if the user that signed in is a row owner on one of the rows in the invite code table, then they will have access to just that one row, so the rest of your logic would work.

1 Like

Yeah but that’s not the data model here: the invite codes aren’t tied to a user’s email, they’re in a separate Invites table.

The idea is that you can hand out invites without having to even know a user’s email or anything about them (e.g. on little pieces of paper), and then anyone with a code can sign in with any account they want to, and use that invite (obviously as long as no one else has already used it).

I’ve created a Query column in the Users table which calculates the first unclaimed invite (i.e. the first one where the invite’s relation to the Users table is empty). I can see it working fine in the Data editor where you can see the current user’s row is the only one not greyed out due to Row Owners:

but when I go to the tab in question in the Layout editor and expand the Data view at the bottom of the screen, the same row is blank.

I guess this is because the query is coming from the Invites table which has Row Owners set and therefore ends up querying on an empty set of data?

Another approach I considered was an action to automatically generate and assign invites to new users as soon as they first sign in. But I don’t see any way to trigger such an action from a sign-up or sign-in, and even creating a button for admins to manually push to achieve the same seems either tricky or perhaps even impossible.

Well, the best I can do is tell you how Glide works. The logistics are something you will need to figure out. Depending on the purpose of your survey, I’d be less concerned about people seeking out unused invite codes and be more concerned about protecting the survey results (which shouldn’t be a problem here).

The data editor is a little skewed as far as representing data under row ownership. The data editor still has access to all rows and will simply grey out unowned rows, but they are still visible and accessible. In reality, the front end builder and the published app will only have access to owned rows.

I guess I don’t understand the point here. If you already convinced a user to sign in, and you automatically generate an invite code, then what’s the point? You already know who the user is. You can can easily see if they have taken the survey or not. All based on user email. What’s the point of an extra step involving an invite code? You can do some sort of onboarding flow to trigger a code generation, but seems overkill…unless you are letting anyone sign in, but only certain users can proceed to the survey.


If you have specific questions about data security, I recommend reading the documentation.

2 Likes

One point is that we need to randomly assign each user to one of six possible groups, and ensure an even distribution of users over those groups. By creating all the invite codes up front, we can assign invite codes to groups and ensure this distribution.

After more searching, I finally found how to add a column with Type > Computed > Experimental > Number > Roll Dice, which was surprisingly undocumented. I’ve posted a separate thread about that:

But now I figured it out, I guess this could help.

However there were two more reasons for using preallocated invite codes.

Firstly, we want to collect all survey responses in a single table, regardless of whether they were collected by one of the admins running the app on their phone and talking with users face-to-face, or whether the users were logging in themselves remotely.

In the former case, the admin would be logged in as themself (to save the extra friction of requiring the user to sign up/in), would get the user to roll a physical die (to make it feel more like a game), and then tap the result of the die roll into an admin screen which would create a new stub entry in the responses table ready to be filled out by the user via the admin’s phone.

In the latter case, yes ideally we could skip the invite code bit, but we would still need a new row in the responses table for that user. And we also want to cap the number of survey responses. Once invite codes are all used up, no more users can participate. If invite codes can be skipped, then some other capping mechanism would be needed. I guess we could maintain a count of all users and add some conditional somewhere to gate on this.

But supporting both use cases (i.e. admin logged in with f2f interaction & users logging in themselves) within the same app seems awkward, especially if only one of them uses invite codes, because then we probably need duplicate copies of several tabs and actions to handle slightly different conditionals and fields.

It’s all a lot harder than I expected :slightly_frowning_face: Nevertheless, massively grateful for your assistance up to this point!