支持Laravel.io的持续发展 →

在您的Laravel应用中添加社交登录:Twitter和GitHub

22 Jun, 2022 16 min read

引言

在您的Laravel应用程序中,您通常会提供使用传统电子邮件和密码表单让用户注册和登录的功能。但是,可能有时您希望允许用户使用第三方服务如Twitter、GitHub和Google等登录到您的应用程序。

在本指南中,我们将探讨如何使用 Laravel Socialite 允许用户使用Twitter登录到您的Laravel应用程序的基本方法。

什么是OAuth和Socialite?

在开始之前,值得回顾并理解Laravel Socialite是什么以及它是如何工作的。Socialite是Laravel团队提供的首选包,它可以让你通过OAuth提供程序进行身份验证,例如:Twitter、GitHub、GitLab、BitBucket、Facebook、LinkedIn和Google。

还有一个由社区驱动的网站 Socialite Providers,它为更多的OAuth提供程序提供支持,例如Apple、Instagram和Dribbble。

如果您以前没有听说过OAuth,但通过Socialite为我们承担大部分重活,您仍然可以跟随此指南。基本上,根据维基百科的定义维基百科,OAuth(开放 授权)是一个 "开放的访问委托标准,通常用于互联网用户授权网站或应用访问他们其他网站上的信息,但不会泄露他们的密码"。如果您曾见过“通过Google登录”、“通过Twitter登录”等网站,那么您很可能已经遵循了OAuth工作流程。

在这篇特定的指南中,我们将使用较新的OAuth 2.0实现,而不是较旧的OAuth 1.0实现。如果您想了解这两个版本之间的区别,可以查看OAuth 1和2的区别文章。

使用Twitter登录

在Twitter中创建应用

在我们触及Laravel项目的任何代码之前,我们首先需要在本站的https://developer.twitter.com上设置一个新的Twitter应用。

如果您还没有注册,您需要先注册并前往仪表板创建一个新项目。

创建新项目后,您还需要创建一个新的Twitter应用,并为其启用OAuth 2.0。当为您的应用启用OAuth时,您可能希望将“应用类型”设置 为“Web应用”。在添加您的“回调URI/重定向URL”时,您想要输入用户允许访问Twitter后应被重定向的精确URL(我们将在以下内容中进行更深入的探讨)。在此特定教程中,我们将使用http://localhost/auth/callback/twitter作为我们的回调URI。但是,您需要确保在此处添加您实时服务器的URL,否则它只能在您的本地开发站点上运行。例如,如果您的网站位于https://my-awesome-app.com,您需要添加本地主机URL以及https://my-awesome-app.com/auth/callback/twitter

要获取更详细的Twitter项目和应用的设置指南,您可以查看Twitter“项目”文档

还值得一提的是,如果您想访问用户的电子邮件地址(您很可能需要这样做),您需要为您的项目申请“提升权限”。没有额外的权限,您将无法查看用户的电子邮件地址。

安装Socialite

要开始使用Socialite,您需要使用以下命令安装laravel/socialite

composer require laravel/socialite

然后,您希望在config/services.php配置文件中将Twitter项目的凭证和我们的回调URL添加到您的twitter-oauth-2字段中,如下

return [

    // ...
    
    'twitter' => [
        'client_id' => env('TWITTER_CLIENT_ID'),
        'client_secret' => env('TWITTER_CLIENT_SECRET'),
        'redirect' => env('OAUTH_CALLBACK_URL'),
    ],
        
    // ...

];

然后,在您的.env文件中,您可以添加以下字段

TWITTER_CLIENT_ID=client-id-goes-here
TWITTER_CLIENT_SECRET=client-secret-goes-here
OAUTH_CALLBACK_URL=http://localhost/auth/callback/twitter

重要的是要记住,您的OAUTH_CALLBACK_URL字段必须是一个绝对URL。例如,您应该使用http://localhost/auth/callback/twitter而不是仅仅使用/auth/callback/twitter

准备数据库

现在我们已经在本站的Twitter上设置了应用,并且已经配置了Socialite,我们可以设置数据库以处理Socialite。我们将想要跟踪用户是否是使用Socialite注册的,并且如果我们要代表用户进行API请求,我们可能还想跟踪它们的令牌。

为了本指南的目的,我假设您在数据库中没有users表,或者还没有创建表的迁移。因此,我们将从创建这样一个表的新的数据库迁移开始,使用以下命令:

php artisan make:migration create_users_table --create=users

这将为我们创建一个新的迁移,位于我们项目的database/migrations文件夹中。接着,我们将更新这个迁移,使其大致如下所示:

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email');
            $table->string('password');
            $table->string('avatar_url');
            $table->string('twitter_id')->nullable();
            $table->string('twitter_token')->nullable();
            $table->string('twitter_refresh_token')->nullable();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('users');
    }
};

然后我们可以运行这个迁移,并使用以下命令将users表添加到我们的数据库中:

php artisan migrate

准备模型

现在数据库已经迁移,我们可以创建我们的User模型了。我们将通过运行以下命令来完成这项工作

php artisan make:model User

然后我们可以更新我们的模型,如下所示

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    protected $fillable = [
        'name',
        'email',
        'password',
        'avatar_url',
        'twitter_id',
        'twitter_token',
        'twitter_refresh_token',
    ];

    protected $hidden = [
        'password',
        'remember_token',
        'twitter_token',
        'twitter_refresh_token',
    ];

    protected $casts = [
        'twitter_token' => 'encrypted',
        'twitter_refresh_token' => 'encrypted',
    ];
}

注意,我们定义了twitter_tokentwitter_refresh_token字段应该被加密。我们将在下文进一步说明这一点。

设置控制器和路由

现在我们已经正确设置了数据库和模型,我们需要添加两个新的路由以及一个控制器来处理这些路由。

这些路由将负责两个操作:

  1. 将用户从我们的Laravel应用程序导向Twitter的路由。这是用户允许通过Twitter在我们的应用程序中认证的地方。
  2. 在用户在Twitter中允许权限后,用户将被重定向到我们的应用程序中的路由。

首先,让我们在routes/web.php文件中创建这些路由,如下所示:

Route::controller(OAuthController::class)->group(function () {
    Route::get('/auth/redirect/twitter', 'redirect')->name('oauth.redirect');
    Route::get('/auth/callback/twitter', 'callback')->name('oauth.callback');
});

然后我们可以创建我们的OAuthController,我们在路由中使用它。为了开始,我们将在控制器中添加我们的redirect方法,将用户重定向到Twitter。

namespace App\Http\Controllers;

use Laravel\Socialite\Facades\Socialite;
use Symfony\Component\HttpFoundation\RedirectResponse;

class OAuthController extends Controller
{
    public function redirect(): RedirectResponse
    {
        return Socialite::driver('twitter-oauth-2')->redirect();
    }

    // ...
}

如您所见,Socialite正在为我们做大部分工作,所以这个方法非常简单。还值得注意的是,我们需要在这里传递twitter-oauth-2而不是仅仅使用twitter,因为我们想使用OAuth 2.0的实施方案而不是OAuth 1.0的实施方案。

既然我们已经添加了将用户重定向到Twitter的路由和控制器方法,我们就需要创建一个新的控制器方法来处理用户返回到网站的情况。出于本例的目的,为了让代码更容易阅读,我将所有代码放在控制器方法中。但请随意根据自己的喜好在您的项目中拆分代码(类似于我在整理Laravel控制器文章中展示的方式)。

因此,我们的新callback方法可能看起来如下:

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Laravel\Socialite\Facades\Socialite;
use Symfony\Component\HttpFoundation\RedirectResponse;

class OAuthController extends Controller
{
    // ...

    public function callback(): RedirectResponse
    {
        $oAuthUser = Socialite::driver('twitter-oauth-2')->user();

        $user = User::updateOrCreate([
            'twitter_id' => $oAuthUser->getId(),
        ], [
            'name' => $oAuthUser->getName(),
            'email' => $oAuthUser->getEmail(),
            'password' => Hash::make(Str::random(50)),
            'avatar_url' => $oAuthUser->getAvatar(),
            'twitter_token' => $oAuthUser->token,
            'twitter_refresh_token' => $oAuthUser->refreshToken,
        ]);

        Auth::login($user);

        return redirect()->route('dashboard');
    }
}

如您所见,Socialite再次做了大部分繁重的工作。但是,让我们逐步了解代码做了什么,以便理解工作流程。

我们首先调用Socialite::driver('twitter-oauth-2')->user()。这是使用从Twitter返回的URL中传递的参数来解决用户的Twitter细节。成功解析用户后,我们可以在$oAuthUser字段上调用许多不同的方法,如下所示:

$oAuthUser->getId();
$oAuthUser->getNickname();
$oAuthUser->getName();
$oAuthUser->getEmail();
$oAuthUser->getAvatar();

因为我们也使用了一个OAuth 2.0提供者,所以我们能够访问以下字段

$oAuthUser->token;
$oAuthUser->refreshToken;
$oAuthUser->expiresIn;

如果无法使用user()方法确定用户,将抛出Laravel\Socialite\Two\InvalidStateException异常。这可能是由多个原因造成的,例如

  • 请求被重放(您只能访问URL一次)。
  • 用户按下“取消”按钮并且不允许通过Twitter进行登录。
  • 某些(或全部)查询参数不正确。这可能是恶意行为者试图查找注册和登录过程中的漏洞的结果。

为了使本指南保持简单,我未对任何这些情况进行处理。但,这可能是在你的项目中添加的功能,而不仅仅是显示一个500错误页。

值得注意的是,在这个特定的教程中,我们只介绍如何使用Twitter作为传统注册表单的替代方案,来登录到你的Laravel应用程序。然而,如果你希望你的Laravel应用程序代表认证用户进行API调用,你将可以利用tokenrefreshToken字段来发起那些请求。例如,如果你正在构建Twitter分析或排程应用程序(如ilo.so),并希望代表用户发布推文,你可能会这样做。出于安全考虑,确实非常重要的是记住,除非你真的需要并打算使用它们,否则你不应该存储这些令牌。你可能还想要在存储之前对它们进行加密,以提供额外的基本安全性。虽然使用你的Laravel应用程序的APP_KEY来加密这些令牌不会防止如果你的应用程序服务器遭到入侵,密钥遭到破坏并解密的风险,但它们至少会在你的数据库遭到破坏时提供一小部分保护。安全地存储密钥是你需要根据项目逐一决定的事情,以制定最适合你(和你的用户)的策略。

在我们控制器中,在解析了我们的Twitter用户之后,我们使用User::updateOrCreate()。这样做是为了检查用户是否过去曾使用Twitter登录到我们的应用程序。如果他们已经登录,我们会更新他们的详细信息,确保我们有他们最新的信息(如姓名、头像和令牌)。如果用户不存在,我们将在数据库中创建他们。然后,我们将认证用户并将他们重定向到应用程序的仪表板。

使用我们在这里使用的方法,你的数据库中可能有多个具有相同电子邮件地址的用户。例如,如果你允许传统的注册或使用Twitter和GitHub登录,这可能会导致你拥有三个具有相同电子邮件地址的用户(每个注册方法一个)。因此,为了应对这种情况,我有时会在控制器的callback方法以及传统的注册表中添加额外的检查,以防止用户使用多种方法注册相同的电子邮件地址。同样,你也可能想要更新你的传统登录表单,以防止OAuth用户尝试登录。在这个例子中,我们创建了一个随机的50个字符的密码,所以用户不太可能能够登录。但是,仍然建议添加一个显式的检查来防止OAuth注册用户登录。然而,这是一个你可能在你项目中允许的事情,因此这是一个你可能需要根据项目逐一更改的事情。

简而言之,在我们完成创建控制器后,它应该看起来像这样

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Laravel\Socialite\Facades\Socialite;
use Symfony\Component\HttpFoundation\RedirectResponse;

class OAuthController extends Controller
{
    public function redirect(): RedirectResponse
    {
        return Socialite::driver('twitter-oauth-2')->redirect();
}

    public function callback(): RedirectResponse
    {
        $oAuthUser = Socialite::driver('twitter-oauth-2')->user();
                
        $user = User::updateOrCreate([
            'twitter_id' => $oAuthUser->getId(),
        ], [
            'name' => $oAuthUser->getName(),
            'email' => $oAuthUser->getEmail(),
            'password' => Hash::make(Str::random(50)),
            'avatar_url' => $oAuthUser->getAvatar(),
            'twitter_token' => $oAuthUser->token,
            'twitter_refresh_token' => $oAuthUser->refreshToken,
        ]);
        
        Auth::login($user);
        
        return redirect()->route('dashboard');
    }
}

深入学习

模型辅助方法

如果你的项目允许使用传统表单和多个OAuth提供程序进行登录,你可能会想要向你的User模型中添加一些有用的方法或访问器,以使你的代码更具可读性。这在你希望根据用户注册的地方执行不同类型的业务逻辑时非常有用。例如,假设你的项目支持使用Twitter和GitHub进行登录。你可以在你的模型中添加以下方法

class User extends Model
{
    // ...
    
    public function isOAuthUser(): bool
    {
        return ! $this->isTwitterUser()
            && ! $this->isGithubUser();
    }
    
    public function isTwitterUser(): bool
    {
        return $this->twitter_id !== null;
    }
    
    public function isGithubUser(): bool
    {
        return $this->github_id !== null;
    }
}

这意味着现在在你的代码中,你可以像这样使用这些方法:$user->isOAuthUser()$user->isTwitterUser()$user->isGithubUser()

错误报告

如果您正在使用第三方错误报告系统(例如 FlareBugsnagHoneybadger 等),您需要确保在错误报告过程中不提交任何与 OAuth 相关的密钥或凭据。例如,您需要确保用户的 twitter_tokentwitter_refresh_token 没有被提交。大多数错误报告系统都提供了一些功能,可以从提交给它们的数据中红冲特定的字段或数据,因此您需要确保阅读必要的文档,以确保您正确地配置了它们。

多个提供商

根据您的项目,您可能想要提供使用其他 OAuth 提供商进行登录的功能。例如,您可能希望允许用户使用 Twitter 或 GitHub 登录。如果是这种情况,我们可以对上面的现有代码进行一些小的修改来实现这一点。对于这部分,我们假设您已经阅读了创建 GitHub 应用程序的设置指南,并将必要的字段添加到了 config/services.php 文件中。

我们可以从使用 PHP 8.1 的枚举类型开始,并创建一个如下所示的枚举

namespace App\Enums;

enum OAuthProvider: string
{
    case Twitter = 'twitter';

    case GitHub = 'github';

    public function driver(): string
    {
        return match ($this) {
            self::Twitter => 'twitter-oauth-2',
            self::GitHub => 'github',
        };
    }
}

然后,我们可以更改我们的路由,接受一个 {provider} 而不是将 twitter 编码为硬编码

Route::controller(OAuthController::class)->group(function () {
    Route::get('/auth/redirect/{provider}', 'redirect')->name('oauth.redirect');
    Route::get('/auth/callback/{provider}', 'callback')->name('oauth.callback');
});

然后,我们可以更新控制器中的 redirect 方法,利用 Laravel 提供的枚举路由绑定

namespace App\Http\Controllers;

use App\Enums\OAuthProvider;
use Laravel\Socialite\Facades\Socialite;
use Symfony\Component\HttpFoundation\RedirectResponse;

class OAuthController extends Controller
{
    public function redirect(OAuthProvider $provider): RedirectResponse
    {
        return Socialite::driver($provider->driver())->redirect();
    }
    
    // ...
}

现在,如果用户导航到 /auth/redirect/twitter/auth/redirect/github,则 $provider->driver() 将返回必要的驱动程序名称(分别为 twitter-oauth-2github)。而如果用户导航到该路由并传递我们列出的 OAuthProvider 枚举中未列出的提供商,则会收到 404 响应。

然后,我们可以这样更新控制器的 callback 方法

namespace App\Http\Controllers;

use App\Enums\OAuthProvider;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Laravel\Socialite\Facades\Socialite;
use Symfony\Component\HttpFoundation\RedirectResponse;

class OAuthController extends Controller
{
    // ...
    
    public function callback(OAuthProvider $provider): RedirectResponse
    {
        $oAuthUser = Socialite::driver($provider->driver())->user();
                
        $user = User::updateOrCreate([
            'oauth_id' => $oAuthUser->getId(),
            'oauth_provider' => $provider,
        ], [
            'name' => $oAuthUser->getName(),
            'email' => $oAuthUser->getEmail(),
            'password' => Hash::make(Str::random(50)),
            'avatar_url' => $oAuthUser->getAvatar(),
            'oauth_token' => $oAuthUser->token,
            'oauth_refresh_token' => $oAuthUser->refreshToken,
        ]);
        
        Auth::login($user);
        
        return redirect()->route('dashboard');
    }
}

在这个方法中,我们删除了有关 Twitter 的所有提及。相反,我们正在使用四个新字段:oauth_idoauth_provideroauth_tokenoauth_refresh_token。这些更改假定项目将只允许用户使用单个提供商进行登录。

要实现这一点,您将需要更新模型以将 oauth_provider 强制转换为 OAuthProvider 枚举实例

namespace App\Models;

use App\Enums\OAuthProvider;
use Illuminate\Database\Eloquent\Model;
    
class User extends Model
{
    protected $fillable = [
        'name',
        'email',
        'password',
        'avatar_url',
        'oauth_id',
        'oauth_provider',
        'oauth_token',
        'oauth_refresh_token',
    ];
    
    protected $hidden = [
        'password',
        'remember_token',
        'oauth_token',
        'oauth_refresh_token',
    ];
    
    protected $casts = [
        'oauth_provider' => OAuthProvider::class,
        'oauth_token' => 'encrypted',
        'oauth_refresh_token' => 'encrypted',
    ];
}

结论

希望这篇帖子能让您了解如何在 Laravel 应用程序中使用 Socialite 允许用户使用 Twitter 登录。它还应为您提供了一些改进和扩展此工作流程以与多个 OAuth 2.0 提供商一起工作的一些想法。

如果您喜欢阅读这篇帖子,我很乐意听您谈谈。同样,如果您有任何改进未来帖子的反馈,我也很乐意听。

如果您想每次我发布新帖子时都得到更新,请随时 订阅我的通讯

继续构建令人惊叹的东西!🚀

上次更新 1 年前。

张三, 李四, 王五, 破晓工作室, 郭静涛代码, 阿图罗GF93 喜欢了这篇文章

6
喜欢这篇文章? 向作者告知,并给他们鼓掌!
ash-jc-allen (Ash Allen) 我是一名来自英国普雷斯顿的自由职业 Laravel 网页开发者。我维护 Ash Allen 设计博客,从事许多酷炫和令人兴奋的项目 🚀

你可能还会喜欢以下文章

2024年3月11日

如何使用Larastan将你的Laravel应用程序从0提高到9

在Laravel应用程序执行前找到bug是可能的,这要归功于Larastan,它是...

阅读文章
2024年7月19日

无需特性行标准化API响应

我注意到,大多数用于API响应的库都是使用特性行实现的,并且...

阅读文章
2024年7月17日

在Laravel项目中通过Discord通知收集反馈

如何创建反馈模块并在收到消息时获取Discord通知

阅读文章

我们想感谢这些 非凡的公司 支持我们

你的logo在这里?

Laravel.io

Laravel解决问题、知识共享和社区建设的门户。

© 2024 Laravel.io - 版权所有。