Most people on their path as developers will end up having to build some sort of authentication system. Many will adapt some solution that is already out there such as AccessPass, Addict, Ueberauth. When it comes to team-based authentication, there are far less resources to learn from and you will most likely have to extend existing libraries. I learned this first-hand in the past week while working on one of my own projects. I wanted to introduce team-based authentication to a project that currently was user-based. The following will cover my thought process on adding team-based authentication to a project using AccessPass. While everyone has different requirements, the following is what worked for me and is general enough that it can be changed to work for most anything.

Before reading more, if you did not read my last post on setting up AccessPass it may be beneficial to do so now.

First, I had to decide on the structure of my data. I was going from a user-based authentication so there was not much thought to that setup. I just had a user table required by AccessPass but changed to this new structure below:

Obviously I am leaving out all the attributes you may normally have in each table. I am just trying to show the relations. In my team-based system, I chose to house permissions in the teams table so that I can infer a user’s permissions based on what teams he/she belongs to.

I am going to do some shortening so that I do not need to show schemas and changeset validations but the big picture of the idea should still be apparent. If you need to touch up on ecto, take a look at their awesome docs.

Next, I added the following to my config so that I could extend AccessPass:

The important line here is insert_override_mod. This will allow me to override the default user insert and wrap it into a transaction like the following:

insert_override_mod expects the file to have a function named insert_override that accepts a changeset. The above looks like a lot so I will break it down step-by-step.

Because of the above config, insert_override is called and passed the changeset from AccessPass on user registration. Running Multi.new() will allow us to “batch” transactions togeather so we do not need to worry about our database state getting all weird if part of the chain of inserts pass while some fail. Multi.insert is, in many ways, like Repo.insert. If passed a valid changeset, it will pass just fine. If not, it will fail. In the case of create_new_account(), create_account_admin_team() and create_user_membership() are excluded from the above snippet. The gist of these functions is they create changesets. I will talk about one caveat below. Multi.run is the bread and butter of the whole operation. It allows us to dig in and pull out some id’s that have been generated on insert and use those as keys to make our foreign key references. You can see above that I am using Multi.run() twice. Once to pull out the account_id from a previous insert and use it as a foreign key on a new team. Second time I am pulling out both team_id and user_id to use as foreign keys on the junction membership table for a new insert. After all the Multi chain is set, we pass it to Repo.transaction() to attempt to run through each part of the chain. For those unfamiliar with transactions, if anything fails at any step, everything in this case that happened before it will be rolled back. This means if, for whatever reason, our membership creation failed, it will roll back the team, user, and account that was created previously. Lastly, we take the result of the Repo.transaction and decode the results, converting it into something that AccessPass expects to receive back from insert_override.

While I omitted a lot of the nitty gritty of the process, I believe this is a good high-level overview of how to structure team-based applications. I was having some trouble at the start of deciding on a database structure to match with how I wanted everything to work. Hopefully this can help someone else in the position I was in.