Scout APM is PHP application performance monitoring designed for developers. With tracing logic that ties issues back to the line of code causing them, you can pinpoint n+1 queries, memory leaks, and other abnormalities in real time so you can knock them out and get back to building a great product. Start your free 14-day trial today and get the performance insight you need in less than 4 minutes.

Selling digital products using Laravel part 2: Logging in using GitHub

Original – by Freek Van der Herten – 4 minute read

Let's take a look at how people can log in to spatie.be in via GitHub. For this functionality, we use a wonderful Laravel first-party package called Socialite.

This is how the login page at spatie.be looks like.

screenshot

Socialite makes it easy to authenticate with OAuth providers. It supports GitHub, Google, Twitter, Facebook, and many others. We don't only use Socialite when logging in, but also to connect an existing spatie.be account to a GitHub account. Connecting your spatie.be account to GitHub can be done on the profile page.

screenshot

This is what it looks like when an account is connected. When connecting an account to GitHub, we also check if the user is a sponsor.

screenshot

Both the "Log in with GitHub" and "Connect To GitHub accounts" go to the same controller, GitHubSocialteController.

Let's take a look at the code of that GitHubSocialteController. The redirect method will redirect to the user to an authentication page on GitHub.

screenshot

The callback method will be called when a user has successfully authenticated at GitHub.

namespace App\Http\Controllers;

use App\Actions\RestoreRepositoryAccessAction;
use App\Models\User;
use App\Services\GitHub\GitHubGraphApi;
use Illuminate\Support\Str;
use Laravel\Socialite\Facades\Socialite;

class GitHubSocialiteController
{
    public function redirect()
    {
        session()->reflash();

        if (auth()->check()) {
            /*
             * If somebody is already logged in, the user wants to
             * connect their GitHub profile. Remember who's logged in.
             */
            session()->put('auth-user-email', auth()->user()->email);
        }

        return Socialite::driver('github')->redirect();
    }

    public function callback()
    {
        $gitHubUser = Socialite::driver('github')->stateless()->user();

        $isSponsor = (new GitHubGraphApi())->isSponsor($gitHubUser->nickname);

        $user = $this->retrieveUser($gitHubUser);

        $user->update([
            'github_id' => $gitHubUser->id,
            'github_username' => $gitHubUser->nickname,
            'avatar' => $gitHubUser->avatar,
            'is_sponsor' => $isSponsor,
        ]);

        /*
         * Make sure the user has access to all repositories that
         * belong to past purchases.
         */
        app(RestoreRepositoryAccessAction::class)->execute($user);

        if (! $user->is_sponsor && ! $user->isSpatieMember()) {
            /*
             * Display a flash warning on the profile page that the user
             * is not yet a sponsor.
             */
            session()->flash('not-a-sponsor');
        }

        auth()->login($user, true);

        flash()->success('You have been logged in');

        return redirect()->to(session('next', route('products.index')));
    }

    protected function retrieveUser($gitHubUser): User
    {
        if (session('auth-user-email')) {
            /*
             * If there already was a local user created for the email used
             * on GitHub, then let's use that local user
             */
            return User::where('email', session('auth-user-email'))->first();
        }

        if ($user = User::where('github_id', $gitHubUser->id)->orWhere('email', $gitHubUser->email)->first()) {
            /*
             * Somebody tries to login via GitHub that already
             * has been logged in, in the past.
             */
            return $user;
        }

        /*
         * Somebody tries to login via GitHub that doesn't have a local user
         * yet. Let's create a new local user.
         */
        return User::create([
            'password' => bcrypt(Str::random()),
            'email' => $gitHubUser->email,
            'name' => $gitHubUser->name ?? $gitHubUser->nickname,
        ]);
    }
}

After a user has successfully logged in (or connected their GitHub account), we have a local User model in our database that we can log in.

You probably noticed that in the code we use the email address from the GitHub profile to retreive the local user. You might think than anybody can take over accounts by creating dummy GitHub accounts that have the same address as Spatie accounts. That isn't possible because GitHub doesn't allow authenticating when an email address isn't verified at their end.

screenshot

This series is continued in part 3: Giving customers access to private repositories on Github.

Stay up to date with all things Laravel, PHP, and JavaScript.

Follow me on Twitter. I regularly tweet out programming tips, and what I myself have learned in ongoing projects.

Every two weeks I send out a newsletter containing lots of interesting stuff for the modern PHP developer.

Expect quick tips & tricks, interesting tutorials, opinions and packages. Because I work with Laravel every day there is an emphasis on that framework.

Rest assured that I will only use your email address to send you the newsletter and will not use it for any other purposes.

Comments

Webmentions

waking lifer liked on 14th October 2020
Robin Dirksen liked on 13th October 2020
Richard Radermacher liked on 13th October 2020