or how to use different passwords for multiple roles for the same account
We have all seen a movie or two where a villain forces some innocent people, to give him the password to access her ultra secret account (a nuclear facility, or a Swiss bank account), the villain ends up getting access to the account and transfers all the money to his own account, or launch a nuclear weapon. We have also all heard about authorities in some countries that force their citizens or even visitors to hand them their social media accounts and passwords. Usually the “victims” don’t have any choice and they end up providing their passwords.
One way to prevent this is maybe to activate multi level authentication on your account (2FA), and leave your phone, where you receive the one time password, at home when you travel, but this might not be a real solution in too many cases.
What if instead of all this, we have systems/websites with multiple roles/password for each account, and instead of using just one password all the time, we can use another one to tell the system “Hey system, when I use this particular password it means that I’m not in a secure place (or I’m forced to hand out my password) so please show just a restricted version of my account”, or even better “Hey system, when I use this particular password just disable my account, and do not accept any request to activate it for 30 days”.
I’ll explain in this article how we would implement such a system , for the sake of simplicity, I’ll focus just on the main concepts, and I’ll be using just a single controller, a single table for the users and their roles, and even use just 3 types of account: master, restricted and a trigger. I’ll be also using PHP/Laravel, but you can implement the same idea with the language of your choice.
In order to add multiple roles for the user, we will be creating additional [sub-]account to the user. To do so, we will create a users table migration that contains:
$table->string('email')->unique()->nullable();$table->integer('master_account_id')->unsigned()->nullable();$table->foreign('master_account_id')->references('id')->on('users');$table->string('type')->nullable();
as you can can see here, the email
is nullable, so we can create new accounts without an email (we can prevent the users from creating new “main” accounts without a password with a validation layer). We are also referencing a master account, and the type of the account.
We can create a main account and two sub-accounts like the following example:
When we build a classical authentication in a Laravel application (without using the built it authController), we usually check if the password we get corresponds to the email like this:
public function postLogin(Request $request){$data = $request->all();$email = $data['email'];$password = $data['password'];if (Auth::attempt(['email' => $email, 'password' => $password]) {// redirect the user to the dashboard} else {// redirect the user back to the login page}…}
in our new authentication system we will change that a little bit
first, we get all the accounts that belongs the email address:
$emailOwner = User::where('email', $email)→first();$users = User::where('master_account_id', $emailOwner->id)->get();
and then we attempt to login to them one by one with the password we received:
foreach ($users as $user) {if (Auth::attempt(['id' => $user->id, 'password' => $password])) {return redirect('/home');}}// if we finish the loop without finding a match, redirect the user back to the login page
note here that we are attempting the authentication with the user ID (not the email) and the password, and as soon as we find a match, we login with the found account.
As you can see here we created a multi-roles account and we chose the role based on the password the user used.
let’s add one more thing, lets add a way to trigger some actions when we login with one of the roles. To keep the example simple, let’s disable all the accounts associated with an email address if we authenticate with a specific password.
First lets add an additional field to the user account:
$table->boolean('disabled')->default(false);
and the PostLogin method (the one we use to authenticate the user) will become like this:
function postLogin(Request $request){$data = $request->all();$email = $data['email'];$emailOwner = User::where('email', $email)->first();
if (!$email) {
return redirect()->route('login')->with('authentication-issue', true);
}
$password = $data\['password'\];
$users = User::where('master\_account\_id', $emailOwner->id)->get();
foreach ($users as $user) {
if (Auth::attempt(\['id' => $user->id, 'password' => $password\])) {
if ($user->type == "trigger") {
$this->trigger($emailOwner->id);
}
if ($user->disabled) {
return redirect()->route('login')->with('account-disabled', true);
}
return redirect('/home');
}
}
return redirect()->route('login')->with('authentication-issue', true);
}
function trigger($userId){$users = User::where('master_account_id', $userId)->get();foreach ($users as $user) {$user->disabled = true;$user->save();}}
when we login to our account with the password associated with the trigger account, the master account and all its sub-accounts get disabled.
You can find the demo in this repo: https://github.com/djug/password-based-roles-actions