Security Tip: Don't Hardcode Admin Emails

[Tip#17] It's easy to forget to update the admins list when it changes...

Security Tip: Don't Hardcode Admin Emails

Hardcoded admin emails (or usernames) are something I’ve seen in almost every codebase I’ve worked on and/or audited. That’s really not surprising though, since the official documentation itself basically tells you to do it like this. However when it comes to security, it’s a terrible idea...

Let’s take Laravel Nova as an example. The Nova documentation tells you to add a Gate definition into your app/Providers/NovaServiceProvider.php to define admin users.

Consider this Gate:

Gate::define('viewNova', function ($user) {
    return in_array($user->email, [
        'frodo@example.com',
        'samwise@example.com',
        'meriadoc@example.com',
        'peregrin@example.com',
        'fredegar@example.com',
    ]);
});

It looks simple and easy to update, but it’s 2 levels deep inside a system class, so it’s super easy to forget about. If one of the admins leaves the company (or loses admin access), there is a good chance their email address will be left in the list and retain admin access.

If their account is compromised or given to someone else, that admin access will be available when it shouldn’t be. This potentially opens up admin access to a hacker or someone with malicious intentions. In this example, Nova gives full access to modify the database - which gives the hacker full access to do whatever they want.

⚠️
Consider the scenario where they are fired and want to get revenge: How quickly can you revoke access, if you need to make a code change, push through a PR, merge and test in CI, and then deploy to your fleet? (Assuming you remember to update the service provider at all!)

Also, if you’re using multiple packages with their own authorization lists, it’s easy to forget some of them when adding/removing admins, leaving lingering access in some areas.

The worst offender is putting the admin list in your javascript. This is sent to the browser, so anyone who discovers it has full access to your list of admins. It’s then trivial to look up credential stuffing lists to find potentially working passwords for a site admin account. 😱

🕵️
If you want to scare yourself, go to Have I Been Pwned and check all of your admin emails. I guarantee at least one will have been Pwned (if it’s not a newish company domain). Also, don’t forget to subscribe to alerts for your domain names , to receive alerts if anyone on your team is Pwned. ↩

What To Do Instead

There are some really simple ways to avoid hardcoding admin emails. I usually either store admin permissions in the database, or define emails in configuration files. It doesn’t really matter how you do it as long as it’s not hardcoded, and is easy to change and review securely.

Here are two example solutions:

1. Define an admin flag on the user models:

Gate::define('viewNova', function ($user) {
    return $user->admin;
});

This also solves the Javascript problem, as the user model data passed to the browser can contain the admin flag too.

2. Store the admins list in .env:

// .env

APP_ADMINS="frodo@example.com,samwise@example.com,meriadoc@example.com,peregrin@example.com,fredegar@example.com"

// config/app.php

'admins' => explode(',', env('APP_ADMINS')),

// NovaServiceProvider

Gate::define('viewNova', function ($user) {
    return in_array($user->email, config('app.admins'));
});

Found this security tip helpful? Don't forget to subscribe to receive new Security Tips each week, and upgrade to a premium subscription to receive monthly In Depth articles, or toss a coin in the tip jar.

Reach out if you're looking for a Laravel Security Audit and Penetration Test or a budget-friendly Security Review, and find me on the various socials through Pinkary. Finally, don't forget to check out Practical Laravel Security, my interactive security course.