在 Laravel 中使用 Passport 和 Lighthouse PHP 开始 GraphQL 身份验证
免责声明:并非每个应用程序都需要 Passport,请参阅文档网站上的“入门”部分,以了解动机和常见用例https://lighthouse-php-auth.com/docs/getting-started/
使用 Lighthouse PHP 和 Laravel 编写 GraphQL API 真是件有趣的事情,但您必须始终做的其中一件事是添加身份验证。这是一个常见任务,Laravel 已经通过 Passport 来处理,但如果您想将登录和刷新令牌端点作为突变来实现,那将是一个很好的主意,因为您无需单独对您的 GraphQL API 进行身份验证机制文档。您也可以通过突变实现。这正是我创作名为 Lighthouse GraphQL Passport Auth 的一个小包的灵感来源。最初,这个名字可能听起来很长,但说真的,确实如此。但让我们先来看看如何使用它,稍后再讨论名字的问题吧 XD。
安装和配置
我们需要做的第一件事是安装包,请注意,除了 Passport,还需要 Lighthouse PHP,所以我们为什么不一起安装呢?在一个全新的 Laravel 应用程序中,请输入:
composer require nuwave/lighthouse laravel/passport joselfonseca/lighthouse-graphql-passport-auth
现在,让我们从Laravel Passport开始配置每个包,为此我们首先运行迁移。
php artisan migrate
然后我们应该运行passport安装命令。
php artisan passport:install
然后,将HasApiTokens特质添加到您的用户模型中
namespace App;
use Laravel\Passport\HasApiTokens;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use HasApiTokens, Notifiable;
}
现在,我们应该注册passport路由,因为我们仍然需要它们以获取内部令牌。
namespace App\Providers;
use Laravel\Passport\Passport;
use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
];
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Passport::routes();
}
}
一旦我们有了这个,就把passport驱动程序添加到config/auth.php文件中的API保护器。
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
],
这应该就完成了passport的配置。
现在让我们配置并安装Lighthouse PHP包。因为已经通过Composer获取了它,我们只需要运行以下命令来发布默认模式。
php artisan vendor:publish --provider="Nuwave\Lighthouse\LighthouseServiceProvider" --tag=schema
此命令将在graphql/schema.graphql文件中创建一个具有以下模式的文件
"A datetime string with format `Y-m-d H:i:s`, e.g. `2018-01-01 13:00:00`."
scalar DateTime @scalar(class: "Nuwave\\Lighthouse\\Schema\\Types\\Scalars\\DateTime")
"A date string with format `Y-m-d`, e.g. `2011-05-23`."
scalar Date @scalar(class: "Nuwave\\Lighthouse\\Schema\\Types\\Scalars\\Date")
type Query {
users: [User!]! @paginate(type: "paginator" model: "App\\User")
user(id: ID @eq): User @find(model: "App\\User")
}
type User {
id: ID!
name: String!
email: String!
created_at: DateTime!
updated_at: DateTime!
}
如果您有这个文件,您应该可以继续使用Lighthouse GraphQL Passport Auth包。
要获取所需的值,请打开您的数据库客户端,从oauth_clients表获取密码和客户端,获取客户端ID和密钥,并将它们放在.env文件中
PASSPORT_CLIENT_ID=2
PASSPORT_CLIENT_SECRET=69YJomGV9plchWIkGD2PyKBBTHfkNA7H83iYGc6j
完成此操作后,发布包配置和默认模式。
php artisan vendor:publish --provider="Joselfonseca\LighthouseGraphQLPassport\Providers\LighthouseGraphQLPassportServiceProvider"
此命令应发布2个文件,即config/lighthouse-graphql-passport.php和graphql/auth.graphql文件。为了方便并能更好地控制认证模式,让我们更新配置文件以使用发布的模式,将schema属性值更改为发布的文件路径,如下所示
'schema' => base_path('graphql/auth.graphql')
这将使我们能够在我们需要扩展或更改解析器时操作模式。
现在,让我们从auth模式中删除用户类型,因为我们已经在默认出口之前有了它。转到graphql/auth/graphql文件,并删除以下类型
type User {
id: ID!
name: String!
email: String!
}
一旦我们这样做,我们需要向模式添加一个默认突变,以便认证可以扩展它,或者我们可以直接将认证突变移动到主模式文件的Mutation类型中。因此,在graphql/auth.graphql文件中寻找此代码,并将其移动到graphql/schema.graphql文件
extend type Mutation {
login(input: LoginInput @spread): AuthPayload! @field(resolver: "Joselfonseca\\LighthouseGraphQLPassport\\GraphQL\\Mutations\\Login@resolve")
refreshToken(input: RefreshTokenInput @spread): RefreshTokenPayload! @field(resolver: "Joselfonseca\\LighthouseGraphQLPassport\\GraphQL\\Mutations\\RefreshToken@resolve")
logout: LogoutResponse! @field(resolver: "Joselfonseca\\LighthouseGraphQLPassport\\GraphQL\\Mutations\\Logout@resolve")
forgotPassword(input: ForgotPasswordInput! @spread): ForgotPasswordResponse! @field(resolver: "Joselfonseca\\LighthouseGraphQLPassport\\GraphQL\\Mutations\\ForgotPassword@resolve")
updateForgottenPassword(input: NewPasswordWithCodeInput @spread): ForgotPasswordResponse! @field(resolver: "Joselfonseca\\LighthouseGraphQLPassport\\GraphQL\\Mutations\\ResetPassword@resolve")
register(input: RegisterInput @spread): RegisterResponse! @field(resolver: "Joselfonseca\\LighthouseGraphQLPassport\\GraphQL\\Mutations\\Register@resolve")
socialLogin(input: SocialLoginInput! @spread): AuthPayload! @field(resolver: "Joselfonseca\\LighthouseGraphQLPassport\\GraphQL\\Mutations\\SocialLogin@resolve")
verifyEmail(input: VerifyEmailInput! @spread): AuthPayload! @field(resolver: "Joselfonseca\\LighthouseGraphQLPassport\\GraphQL\\Mutations\\VerifyEmail@resolve")
updatePassword(input: UpdatePassword! @spread): UpdatePasswordResponse! @field(resolver: "Joselfonseca\\LighthouseGraphQLPassport\\GraphQL\\Mutations\\UpdatePassword@resolve") @guard(with: ["api"])
}
然后删除extend字词
type Mutation {
login(input: LoginInput @spread): AuthPayload! @field(resolver: "Joselfonseca\\LighthouseGraphQLPassport\\GraphQL\\Mutations\\Login@resolve")
refreshToken(input: RefreshTokenInput @spread): RefreshTokenPayload! @field(resolver: "Joselfonseca\\LighthouseGraphQLPassport\\GraphQL\\Mutations\\RefreshToken@resolve")
logout: LogoutResponse! @field(resolver: "Joselfonseca\\LighthouseGraphQLPassport\\GraphQL\\Mutations\\Logout@resolve")
forgotPassword(input: ForgotPasswordInput! @spread): ForgotPasswordResponse! @field(resolver: "Joselfonseca\\LighthouseGraphQLPassport\\GraphQL\\Mutations\\ForgotPassword@resolve")
updateForgottenPassword(input: NewPasswordWithCodeInput @spread): ForgotPasswordResponse! @field(resolver: "Joselfonseca\\LighthouseGraphQLPassport\\GraphQL\\Mutations\\ResetPassword@resolve")
register(input: RegisterInput @spread): RegisterResponse! @field(resolver: "Joselfonseca\\LighthouseGraphQLPassport\\GraphQL\\Mutations\\Register@resolve")
socialLogin(input: SocialLoginInput! @spread): AuthPayload! @field(resolver: "Joselfonseca\\LighthouseGraphQLPassport\\GraphQL\\Mutations\\SocialLogin@resolve")
verifyEmail(input: VerifyEmailInput! @spread): AuthPayload! @field(resolver: "Joselfonseca\\LighthouseGraphQLPassport\\GraphQL\\Mutations\\VerifyEmail@resolve")
updatePassword(input: UpdatePassword! @spread): UpdatePasswordResponse! @field(resolver: "Joselfonseca\\LighthouseGraphQLPassport\\GraphQL\\Mutations\\UpdatePassword@resolve") @guard(with: ["api"])
}
现在,您应该有如下所示的方案文件
graphql/auth.graphql
input LoginInput {
username: String!
password: String!
}
input RefreshTokenInput {
refresh_token: String
}
type User {
id: ID!
name: String!
email: String!
}
type AuthPayload {
access_token: String
refresh_token: String
expires_in: Int
token_type: String
user: User
}
type RefreshTokenPayload {
access_token: String!
refresh_token: String!
expires_in: Int!
token_type: String!
}
type LogoutResponse {
status: String!
message: String
}
type ForgotPasswordResponse {
status: String!
message: String
}
type RegisterResponse {
tokens: AuthPayload
status: RegisterStatuses!
}
type UpdatePasswordResponse {
status: String!
message: String
}
enum RegisterStatuses {
MUST_VERIFY_EMAIL
SUCCESS
}
input ForgotPasswordInput {
email: String! @rules(apply: ["required", "email"])
}
input NewPasswordWithCodeInput {
email: String! @rules(apply: ["required", "email"])
token: String! @rules(apply: ["required", "string"])
password: String! @rules(apply: ["required", "confirmed", "min:8"])
password_confirmation: String!
}
input RegisterInput {
name: String! @rules(apply: ["required", "string"])
email: String! @rules(apply: ["required", "email", "unique:users,email"])
password: String! @rules(apply: ["required", "confirmed", "min:8"])
password_confirmation: String!
}
input SocialLoginInput {
provider: String! @rules(apply: ["required"])
token: String! @rules(apply: ["required"])
}
input VerifyEmailInput {
token: String!
}
input UpdatePassword {
old_password: String!
password: String! @rules(apply: ["required", "confirmed", "min:8"])
password_confirmation: String!
}
graphql/schema.graphql
"A datetime string with format `Y-m-d H:i:s`, e.g. `2018-01-01 13:00:00`."
scalar DateTime @scalar(class: "Nuwave\\Lighthouse\\Schema\\Types\\Scalars\\DateTime")
"A date string with format `Y-m-d`, e.g. `2011-05-23`."
scalar Date @scalar(class: "Nuwave\\Lighthouse\\Schema\\Types\\Scalars\\Date")
type Query {
users: [User!]! @paginate(type: "paginator" model: "App\\User")
user(id: ID @eq): User @find(model: "App\\User")
}
type Mutation {
login(input: LoginInput @spread): AuthPayload! @field(resolver: "Joselfonseca\\LighthouseGraphQLPassport\\GraphQL\\Mutations\\Login@resolve")
refreshToken(input: RefreshTokenInput @spread): RefreshTokenPayload! @field(resolver: "Joselfonseca\\LighthouseGraphQLPassport\\GraphQL\\Mutations\\RefreshToken@resolve")
logout: LogoutResponse! @field(resolver: "Joselfonseca\\LighthouseGraphQLPassport\\GraphQL\\Mutations\\Logout@resolve")
forgotPassword(input: ForgotPasswordInput! @spread): ForgotPasswordResponse! @field(resolver: "Joselfonseca\\LighthouseGraphQLPassport\\GraphQL\\Mutations\\ForgotPassword@resolve")
updateForgottenPassword(input: NewPasswordWithCodeInput @spread): ForgotPasswordResponse! @field(resolver: "Joselfonseca\\LighthouseGraphQLPassport\\GraphQL\\Mutations\\ResetPassword@resolve")
register(input: RegisterInput @spread): RegisterResponse! @field(resolver: "Joselfonseca\\LighthouseGraphQLPassport\\GraphQL\\Mutations\\Register@resolve")
socialLogin(input: SocialLoginInput! @spread): AuthPayload! @field(resolver: "Joselfonseca\\LighthouseGraphQLPassport\\GraphQL\\Mutations\\SocialLogin@resolve")
verifyEmail(input: VerifyEmailInput! @spread): AuthPayload! @field(resolver: "Joselfonseca\\LighthouseGraphQLPassport\\GraphQL\\Mutations\\VerifyEmail@resolve")
updatePassword(input: UpdatePassword! @spread): UpdatePasswordResponse! @field(resolver: "Joselfonseca\\LighthouseGraphQLPassport\\GraphQL\\Mutations\\UpdatePassword@resolve") @guard(with: ["api"])
}
type User {
id: ID!
name: String!
email: String!
created_at: DateTime!
updated_at: DateTime!
}
最后,我们需要安装我们将用于向GraphQL服务器发送查询和突变的GraphQL playground,为此请运行以下命令。
composer require mll-lab/laravel-graphql-playground
现在可以开始测试了!
测试认证突变
让我们先运行PHP开发服务器
php artisan serve
这将打开本地主机上的端口8000,以运行项目,让我们导航到http://127.0.0.1:8000/graphql-playground,您应该看到如下所示的playground
现在,让我们使用简单的控制台命令创建一个用户,这样我们就可以使用GraphQL API进行登录。在routes/console.php文件中输入
Artisan::command('user', function () {
\App\User::create([
'name' => 'Jose Fonseca',
'email' => '[email protected]',
'password' => bcrypt('123456789qq')
]);
})->describe('Create sample user');
然后在控制台运行此命令
php artisan user
现在,我们已经有一个用户用于测试,让我们在GraphQL Playground运行以下突变
mutation {
login(input: {
username: "[email protected]",
password: "123456789qq"
}) {
access_token
refresh_token
expires_in
token_type
user {
id
email
name
created_at
updated_at
}
}
}
这应该给以下响应
{
"data": {
"login": {
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6Ijk3NDE3MTI1YjBmNjQyZDNkZDljMDkwOGJiNTM2NDQ5YTYwZWU5ZTQ0ZTBkYzEzY2E5M2FhNGFjODI5ZWRiNzkwMWZhZmY5NzVhZTc5MjRiIn0.eyJhdWQiOiIyIiwianRpIjoiOTc0MTcxMjViMGY2NDJkM2RkOWMwOTA4YmI1MzY0NDlhNjBlZTllNDRlMGRjMTNjYTkzYWE0YWM4MjllZGI3OTAxZmFmZjk3NWFlNzkyNGIiLCJpYXQiOjE1NjE0MDkyMDMsIm5iZiI6MTU2MTQwOTIwMywiZXhwIjoxNTkzMDMxNjAzLCJzdWIiOiIxIiwic2NvcGVzIjpbXX0.CZOpXN0mFHXAQexpA4-FPMOcV-bdZRJD7zUK7guzethg9KUMrALhzgwfQZJv3w74Gnj54aAbdswHBwZEt9DREyEer7Ht0c8108amOllPqb-ydLET1RL1oYCE9H9vvUK0ZafLp09pMrXKot6UcViGOy97KF7YilAvaFfyGxSOmTTZyn0noe9F2ztIOPd3u9XuPuTR5yL-NqHufTTtyJkdQ2xPo03bF4tRfpMXQ5prnIJi4rxmBkpASwMwVraL4lVSZg_9STWMxXWWFdvmXydkNUtQSAftQyHmwMy33OOTxRtFRDN_1Y9wW7U9okVRM-gindkx0o_EB7ekcP1mvHc2PwxwWPMfFxezex98wYX3jTo8y7CN4vDrgUdXqrKkFc6JzwBgn8q_f7c5SzbzxR824h6ujFzSgMaUk_8zKtHX_qgDqaqPVzTebazQ0Pu9PNoYcCkQi5bNldCGuuaMsMxz3H-CWstR4_pAj9_jeKdvC5MA0OkQ30b3RlSmhSqb65LfZEU-y3wG62FKHD49JxBOpPh_Ga8SOQvfOCIL3SzURX9uOvSgcprQqLBYkhJNJC0gobAFgrKHbDhrBVvGH5U4BbIPVX-gnhR44aoyIf8sXFJQkPJJ7-p8HrCqDqjahrlDXsf4DRZGaVJJFhX2VcWAkCcQA2yJ1LSS1XpNU5oL-x4",
"refresh_token": "def502004531ba2a174546464239e59c3d07332f8aec44bf95db59ba578bcb58c1986dd494d887c79282daf7379d2959b35816e07245ad237e5fa5e6b3ea1ff19ca0058aa82abc4d0abad427eb045b894c7591e4e248d684ea0c6772011ac1723d3d63805e0e148ec0875277072e39d9ecafcbe55d29b28607dc3ea9f85bdc0ca88aadca41b97e8f061b9b326dc922708f6826aee7e2e3296891e923fd9b5386b4c99d46e9832cb8e191c63bb31394a3dc92a8fc1b907925e9c1888d8b0c4d8c30ea330785dd429623e93979670856fdb5bf80c6c8cac08dc7d040d796e535aa082e241dd38b4ab7a4b0055d554cd85888533c76af594bed20dddcded751bfecd0ffdb741169f204c732cef0e2c49c26ec81de9dd8099b5ab1a481283f70c9685a11ca7018c259688601359722aee0f527c95b8b48062be81d414ce3e10a334d8b5c4f64c25b248c0b76bc40596b99580e38310e7d3a4e82c910276645101d821f",
"expires_in": 31622400,
"token_type": "Bearer",
"user": {
"id": "1",
"email": "[email protected]",
"name": "Jose Fonseca",
"created_at": "2019-06-24 20:43:53",
"updated_at": "2019-06-24 20:43:53"
}
}
}
}
就是这样!现在您可以在GraphQL API中使用令牌了!
测试受保护的查询
让我们修改users查询,使其需要passport认证,为此请打开graphql/schema.graphql文件,并更新users查询如下
users: [User!]! @guard(with: ["api"]) @paginate(type: "paginator" model: "App\\User")
现在尝试运行users查询
{
users(count: 20){
data {
id
email
}
}
}
这将给出如下所示的认证错误
{
"errors": [
{
"debugMessage": "Unauthenticated.",
"message": "Internal server error",
"extensions": {
"category": "internal"
},
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"users"
],
"trace": [
...
}
这意味着查询需要在请求中包含访问令牌。为了在GraphQL Playground中做到这一点,请添加包含我们在之前生成的令牌的授权标头
重新运行查询,您应该得到正确的结果!
如您所见,查询受保护,并且您现在可以在GraphQL API中使用认证。
现在,尝试其余的突变可用,如刷新令牌或重置密码。
如果您喜欢这个小包,请开始在Github上启动它,并且如果遇到任何问题,请告诉我。
Github: https://github.com/joselfonseca/lighthouse-graphql-passport-auth
文档:https://lighthouse-php-auth.com/
编写愉快!
joedixon, driesvints, lkar-2020 赞同了这篇文章