DEV Community

Caleb Anthony
Caleb Anthony

Posted on • Originally published at cardboardmachete.com

Creating and working with anonymous users in Laravel

First off, I'm defining an "anonymous user" as the user who signs in via a "guest login". A one-tap way to create a new guest account and get in-game to try things out without pledging your email or committing a new password to memory.

With that out of the way...


A nagging issue I see in the PBBG (persistent browser-based game) space is games with little to no information on the home page other than a login and registration form.

New players want to know what your game is like! And most people are unwilling to fill out a registration form to get a peek at it.

The easiest (and most oft-requested) solution to start is adding screenshots to your home page. Show potential players what your game looks like. Explain the major features. Sell your game!

The next-best solution (in my opinion) is to add a guest login.

Allow your future players to tap a single button, get in-game, and get their feet wet. All with no commitment.

Sure, you could design a walkthrough or a tutorial. But most of the types of games we're creating interact with the database very early on. While you can mock out some of the details by storing user data in the session (for example), I have found it much easier to just create a concrete User implementation. That way I can easily tie it to other concrete models like inventory systems and more.

Then, after they're killed their first few monsters or started their first kingdom or completed their first quest, let them know they need to register to save their progress! Players who are going to stick around will happily register, while those who don't like your game will fall off.

Some of the marketing benefits of this type of system:

  1. You get a much lower bounce rate on your home page.
  2. You get to design your onboarding process clearly and concretely for new players.
  3. You get better data and metrics for how many players register, or where exactly they got stuck in-game.

So let's walk through how I designed the anonymous user functionality for Trounced (new kingdom-builder browser game) in Laravel.

1. Update your migrations

The users table that comes with a new Laravel installation requires that you have both the name and password fields filled out. We'll update those so they can be nullable.

Schema::create('users', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->string('email')->nullable()->unique();
    $table->string('password')->nullable();
    // Your other table fields here...
});
Enter fullscreen mode Exit fullscreen mode

The result being that you can now create users without any email or password at all.

Great!

2. Create a Guest Login controller

I created a new controller to handle all the guest logic in one place. The first method we're going to add is a one-click guest login. We'll add the registration method to it later.

class GuestLoginController extends Controller
{
    public function login(): RedirectResponse
    {
        $randomNumbers = rand(pow(10, 5 - 1), pow(10, 5) - 1);
        $guest = User::create([
            'name' => "Guest{$randomNumbers}",
        ]);

        Auth::loginUsingId($guest->id);
        return redirect()->whereverYouWant();
    }
}
Enter fullscreen mode Exit fullscreen mode

This lets you create a new user with a random 5-digit number after the name 'Guest'. Remember, we don't need any other fields to create a valid user.

Now wire up this route to a button on your home page. I'd also recommend sticking it behind some kind of rate limiter so you don't get any bad actors abusing your system.

// app/Providers/RouteServiceProvider.php

RateLimiter::for('guest-registration', function (Request $request) {
    return Limit::perMinute(2)->by($request->ip());
});
Enter fullscreen mode Exit fullscreen mode
// routes/web.php

Route::post('/guest-login', [GuestLoginController::class, 'login'])
    ->middleware(['throttle:guest-registration']);
Enter fullscreen mode Exit fullscreen mode

3. Interacting with 'Guest' users

At some point we'll want to encourage our guests to register, but for now we will interact with them just as guests.

The easiest way to check if a user is a guest or not is by seeing if their email is null.

I have opted to put this as an attribute on the User model to easily tell if a user is a guest:

// User Model
public function isGuest(): Attribute
{
    return new Attribute(
        get: fn ($v, $attr) => $attr['email'] === null,
    );
}
Enter fullscreen mode Exit fullscreen mode

Now you can limit any functionality you want to keep from guest users.

On my front-end, I hide parts of the UI or show different messages to guest users depending on the isGuest attribute that I pass to the front end.

On my back-end I put specific checks in place to refuse certain functionality to guest users.

The implementation of this is now very straightforward and entirely up to you.

4. Encouraging registration

I tend toward being overly communicative to guest users (read: annoying).

I stick a big fat brightly-colored banner on every single page of the game that says "You're currently playing a Guest account. Click here to save your progress!". Clicking the banner takes them to the registration page.

You may opt to be a bit more subtle, or encourage guests to register after an initial tutorial.

This is where you might want to consider the marketing side of things a bit more. Do you want guests to convert to users no matter what? Do you prefer to discourage players from registering until they reach a certain point of the game? This is entirely up to you.

5. Guest Registration

Instead of creating a whole new user, we just want to convert the player's existing guest account into a fully-fledged account by having them give us a name, email and password.

In Trounced, clicking on the annoying persistent banner takes the guest to a registration page where we collect exactly that information from them via a form.

On the back-end, I validate this request and update the guest user. You could even throw in some additional rewards here if you wanted, as incentive to get guests to register their account.

// GuestLoginController

public function store(StoreGuestRequest $request): RedirectResponse
{
    Auth::user()->name = $request->get('name');
    Auth::user()->email = $request->get('email');
    Auth::user()->password = Hash::make($request->get('password'));
    Auth::user()->save();

    return redirect()->backToTheGame();
}
Enter fullscreen mode Exit fullscreen mode

Then in my StoreGuestRequest, I make sure I'm getting all the required info and only allowing guests to hit this endpoint.

class StoreGuestRequest extends FormRequest
{
    public function authorize()
    {
        return Auth::user()->isGuest;
    }

    public function rules()
    {
        return [
            'name' => ['required', 'string', 'max:255'],
            'email' => ['required', 'email', 'max:255', Rule::unique('users')->ignore(Auth::id())],
            'password' => ['required', 'string', new Password, 'confirmed'],
        ];
    }
}
Enter fullscreen mode Exit fullscreen mode

Now that your user has a valid email, the isGuest attribute will always return false and your game will function as normal. Full circle!

6. Cleaning up old Guest accounts

If someone comes and tries out your game but it's not for them, you might not want that Guest69420 user hanging out in your database forever.

In that case, we'll make the User model prunable, which is Laravel's built-in way of cleaning up old models.

We add the Prunable trait to the User model and then define a new prunable method to define the appropriate criteria. Check the documentation here, it describes all the setup.

In my case, any User models that still have a null email and were created 3 or more days ago should be deleted. Feel free to change the criteria, but remember that once a user loses authentication (such as session expiring) with your system, there's no way to re-authenticate them since there's no login information!

// User Model
public function prunable()
{
    return static::whereNull('email')
        ->where('created_at', '<', now()->subDays(3));
}
Enter fullscreen mode Exit fullscreen mode

Now, in our app/Console/Kernel.php we'll register the command to prune the model regularly:

protected function schedule(Schedule $schedule)
{
    $schedule->command('model:prune', [
        '--model' => [User::class],
    ])->daily();
}
Enter fullscreen mode Exit fullscreen mode

Be sure to also clean up any related models (such as inventories) so you don't have useless data floating around in the ether. This is typically best done in an observer, which is outside the scope of this post.

Now old Guest users are automatically cleaned up and thrown out of the database.


That's all, folks!

As with everything in programming, I'm sure there are other (better) ways to accomplish this same type of functionality. For me, I needed a simple way to get players in-game quickly without getting hung up on a registration form.

This bit of logic does exactly that, and it doesn't interfere with any other areas of my application or require me to write lots of abstract conditional logic.

Hopefully it can benefit you, too!

Top comments (0)