From c774f735a96b342627818bb0aa74d2ca9795b042 Mon Sep 17 00:00:00 2001 From: Daniel Lindenkreuz Date: Fri, 26 Jun 2020 10:36:25 +0200 Subject: [PATCH 1/7] add docs to clarify usage with laravel sanctum --- docs/master/security/authentication.md | 79 +++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 1 deletion(-) diff --git a/docs/master/security/authentication.md b/docs/master/security/authentication.md index b228e28799..975491efe5 100644 --- a/docs/master/security/authentication.md +++ b/docs/master/security/authentication.md @@ -45,12 +45,31 @@ This setting is used whenever Lighthouse looks for an authenticated user, for ex such as [@guard](../api-reference/directives.md#guard), or when applying the `AttempAuthentication` middleware. Stateless guards are recommended for most use cases, such as the default `api` guard. -If you are using [Laravel Sanctum](https://laravel.com/docs/master/sanctum) for your API, set it here: + +### Laravel Sanctum + +If you are using [Laravel Sanctum](https://laravel.com/docs/master/sanctum) for your API, set the guard +to `sanctum` and register Sanctum's `EnsureFrontendRequestsAreStateful` as first middleware for Lighthouse's route. ```php + 'route' => [ + // ... + 'middleware' => [ + \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, + // ... other middlewares + ] + ], 'guard' => 'sanctum', ``` +Note that Sanctum requires you to send an CSRF token as [header](https://laravel.com/docs/7.x/csrf#csrf-x-csrf-token) +with all GraphQL requests, regardless of whether the user is authenticated or not. +When using [laravel-graphql-playground](https://github.com/mll-lab/laravel-graphql-playground), follow the [instructions +to add a CSRF token](https://github.com/mll-lab/laravel-graphql-playground#configure-session-authentication). + +For authenticated queries, the request must contain credentials as well. In GraphQL Playground, +add this setting to include the session cookie in all requests: `"request.credentials": "same-site"` + ## Guard selected fields If you want to guard only selected fields, you can use the [@guard](../api-reference/directives.md#guard) @@ -94,3 +113,61 @@ or `null` if the request is not authenticated. } } ``` + +## Login and Logout Mutations + +You can create or destroy a session with mutations instead of separate API endpoints (`/login`, `/logout`). +**Note that this only works when Lighthouse's guard uses a session driver.** Laravel's token based authentication +does not allow logging in or out on the server side. + +```graphql +type Mutation { + login(email: String!, password: String!): User + logout: Boolean @guard +} +``` + +```php +// Generate skeleton with `php artisan lighthouse:mutation login` + +class Login +{ + public function __invoke($rootValue, array $args, GraphQLContext $context, ResolveInfo $resolveInfo) + { + $guard = Auth::guard(); + // When using Laravel Sanctum, always use Sanctum's guard to create a session: + // $guard = Auth::guard(config('sanctum.guard', 'web')); + if ($guard->user()) { + return $guard->user(); + } + + $credentials = collect($args)->only('email', 'password')->all(); + throw_unless( + $guard->attempt($credentials), + AuthenticationException::class + ); + return $guard->user(); + } +} +``` + +```php +// Generate skeleton with `php artisan lighthouse:mutation logout` + +class Logout +{ + public function __invoke($rootValue, array $args, GraphQLContext $context, ResolveInfo $resolveInfo) + { + // When using Laravel Sanctum, always use Sanctum's guard to destroy a session: + // $guard = Auth::guard(config('sanctum.guard', 'web')); + $guard = Auth::guard(); + $guard->logout(); + return !$guard->check(); + } +} + +``` + +If you are using [Laravel Sanctum](https://laravel.com/docs/master/sanctum), you should use Sanctum's guard +to handle sessions. Sanctum supports both session-based and token-based authentication. The example implementations above +only make sense when using session-based authentication. From 25c4d5b870f2f09b0ebec37e58da4e6746d81202 Mon Sep 17 00:00:00 2001 From: Daniel Lindenkreuz Date: Fri, 26 Jun 2020 10:41:45 +0200 Subject: [PATCH 2/7] example code formatting --- docs/master/security/authentication.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/master/security/authentication.md b/docs/master/security/authentication.md index 975491efe5..baff00f766 100644 --- a/docs/master/security/authentication.md +++ b/docs/master/security/authentication.md @@ -135,8 +135,10 @@ class Login public function __invoke($rootValue, array $args, GraphQLContext $context, ResolveInfo $resolveInfo) { $guard = Auth::guard(); + // When using Laravel Sanctum, always use Sanctum's guard to create a session: // $guard = Auth::guard(config('sanctum.guard', 'web')); + if ($guard->user()) { return $guard->user(); } @@ -158,9 +160,11 @@ class Logout { public function __invoke($rootValue, array $args, GraphQLContext $context, ResolveInfo $resolveInfo) { + $guard = Auth::guard(); + // When using Laravel Sanctum, always use Sanctum's guard to destroy a session: // $guard = Auth::guard(config('sanctum.guard', 'web')); - $guard = Auth::guard(); + $guard->logout(); return !$guard->check(); } From b879f1489ffe8fb8389c0c2a884c597386c4abe6 Mon Sep 17 00:00:00 2001 From: Daniel Lindenkreuz Date: Fri, 26 Jun 2020 10:51:19 +0200 Subject: [PATCH 3/7] clarify login / logout mutation implementation --- docs/master/security/authentication.md | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/docs/master/security/authentication.md b/docs/master/security/authentication.md index baff00f766..22edb80cbf 100644 --- a/docs/master/security/authentication.md +++ b/docs/master/security/authentication.md @@ -120,6 +120,24 @@ You can create or destroy a session with mutations instead of separate API endpo **Note that this only works when Lighthouse's guard uses a session driver.** Laravel's token based authentication does not allow logging in or out on the server side. +This requires the following middlewares to be added to `config/Lighthouse.php`: + +```php + 'route' => [ + // ... + 'middleware' => [ + \Illuminate\Cookie\Middleware\EncryptCookies::class, + \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, + \Illuminate\Session\Middleware\StartSession::class, + + // When using Laravel Sanctum, replace the middleware above with only: + // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class + + // ... other middleware + ] + ], +``` + ```graphql type Mutation { login(email: String!, password: String!): User @@ -128,7 +146,7 @@ type Mutation { ``` ```php -// Generate skeleton with `php artisan lighthouse:mutation login` +// Example mutation resolver class Login { @@ -154,7 +172,7 @@ class Login ``` ```php -// Generate skeleton with `php artisan lighthouse:mutation logout` +// Example mutation resolver class Logout { @@ -172,6 +190,6 @@ class Logout ``` -If you are using [Laravel Sanctum](https://laravel.com/docs/master/sanctum), you should use Sanctum's guard +If you are using [Laravel Sanctum](https://laravel.com/docs/master/sanctum), you should use Sanctum's guard in your mutation resolvers to handle sessions. Sanctum supports both session-based and token-based authentication. The example implementations above only make sense when using session-based authentication. From ef6fb2a3922e7268f880e2d44b0028142b0bef86 Mon Sep 17 00:00:00 2001 From: Daniel Lindenkreuz Date: Fri, 26 Jun 2020 10:55:12 +0200 Subject: [PATCH 4/7] fix middleware plural --- docs/master/security/authentication.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/master/security/authentication.md b/docs/master/security/authentication.md index 22edb80cbf..183cad72c3 100644 --- a/docs/master/security/authentication.md +++ b/docs/master/security/authentication.md @@ -120,7 +120,7 @@ You can create or destroy a session with mutations instead of separate API endpo **Note that this only works when Lighthouse's guard uses a session driver.** Laravel's token based authentication does not allow logging in or out on the server side. -This requires the following middlewares to be added to `config/Lighthouse.php`: +This requires the following middleware to be added to `config/Lighthouse.php`: ```php 'route' => [ From 830fdd3bef4adc06c777f58c0910c1c60ae8aeb8 Mon Sep 17 00:00:00 2001 From: Daniel Lindenkreuz Date: Fri, 26 Jun 2020 11:53:56 +0200 Subject: [PATCH 5/7] Update docs/master/security/authentication.md Co-authored-by: Benedikt Franke --- docs/master/security/authentication.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/master/security/authentication.md b/docs/master/security/authentication.md index 183cad72c3..8bdee683de 100644 --- a/docs/master/security/authentication.md +++ b/docs/master/security/authentication.md @@ -62,7 +62,7 @@ to `sanctum` and register Sanctum's `EnsureFrontendRequestsAreStateful` as first 'guard' => 'sanctum', ``` -Note that Sanctum requires you to send an CSRF token as [header](https://laravel.com/docs/7.x/csrf#csrf-x-csrf-token) +Note that Sanctum requires you to send an CSRF token as [header](https://laravel.com/docs/csrf#csrf-x-csrf-token) with all GraphQL requests, regardless of whether the user is authenticated or not. When using [laravel-graphql-playground](https://github.com/mll-lab/laravel-graphql-playground), follow the [instructions to add a CSRF token](https://github.com/mll-lab/laravel-graphql-playground#configure-session-authentication). From 2bd3e6e5bd80d33b57e9a49fd3dac255d7ac7baf Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Fri, 17 Jul 2020 13:24:32 +0200 Subject: [PATCH 6/7] Update example code --- docs/master/security/authentication.md | 91 ++++++++++++++------------ 1 file changed, 50 insertions(+), 41 deletions(-) diff --git a/docs/master/security/authentication.md b/docs/master/security/authentication.md index 8bdee683de..a22a8abf87 100644 --- a/docs/master/security/authentication.md +++ b/docs/master/security/authentication.md @@ -55,8 +55,9 @@ to `sanctum` and register Sanctum's `EnsureFrontendRequestsAreStateful` as first 'route' => [ // ... 'middleware' => [ + // ... other middleware + \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, - // ... other middlewares ] ], 'guard' => 'sanctum', @@ -64,12 +65,10 @@ to `sanctum` and register Sanctum's `EnsureFrontendRequestsAreStateful` as first Note that Sanctum requires you to send an CSRF token as [header](https://laravel.com/docs/csrf#csrf-x-csrf-token) with all GraphQL requests, regardless of whether the user is authenticated or not. + When using [laravel-graphql-playground](https://github.com/mll-lab/laravel-graphql-playground), follow the [instructions to add a CSRF token](https://github.com/mll-lab/laravel-graphql-playground#configure-session-authentication). -For authenticated queries, the request must contain credentials as well. In GraphQL Playground, -add this setting to include the session cookie in all requests: `"request.credentials": "same-site"` - ## Guard selected fields If you want to guard only selected fields, you can use the [@guard](../api-reference/directives.md#guard) @@ -114,82 +113,92 @@ or `null` if the request is not authenticated. } ``` -## Login and Logout Mutations +## Stateful Authentication Example You can create or destroy a session with mutations instead of separate API endpoints (`/login`, `/logout`). -**Note that this only works when Lighthouse's guard uses a session driver.** Laravel's token based authentication -does not allow logging in or out on the server side. +**This only works when Lighthouse's guard uses a session driver.** +Laravel's token based authentication does not allow logging in or out on the server side. -This requires the following middleware to be added to `config/Lighthouse.php`: +The implementation in the docs is only an example and may have to be adapted to your specific use case. + +Add the following middleware to `config/lighthouse.php`: ```php 'route' => [ // ... 'middleware' => [ + // Either those for plain Laravel: \Illuminate\Cookie\Middleware\EncryptCookies::class, \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, \Illuminate\Session\Middleware\StartSession::class, - // When using Laravel Sanctum, replace the middleware above with only: - // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class + // Or this one when using Laravel Sanctum: + \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class // ... other middleware ] ], ``` +The `login` and `logout` might be defined and implement like this: + ```graphql type Mutation { - login(email: String!, password: String!): User - logout: Boolean @guard + "Log in to a new session and get the user." + login(email: String!, password: String!): User! + + "Log out from the current session, showing the user one last time." + logout: User @guard } ``` ```php -// Example mutation resolver - class Login { - public function __invoke($rootValue, array $args, GraphQLContext $context, ResolveInfo $resolveInfo) + /** + * @param null $_ + * @param array $args + */ + public function __invoke($_, array $args): User { - $guard = Auth::guard(); - - // When using Laravel Sanctum, always use Sanctum's guard to create a session: - // $guard = Auth::guard(config('sanctum.guard', 'web')); - - if ($guard->user()) { - return $guard->user(); + // Plain Laravel: Auth::guard() + // Laravel sanctum: Auth::guard(config('sanctum.guard', 'web')) + $guard = ?; + + if( ! $guard->attempt($args)) { + throw new Error('Invalid credentials.'); } - $credentials = collect($args)->only('email', 'password')->all(); - throw_unless( - $guard->attempt($credentials), - AuthenticationException::class - ); - return $guard->user(); + /** + * Since we successfully logged in, this can no longer be `null`. + * + * @var \App\Models\User $user + */ + $user = $guard->user(); + + return $user; } } ``` ```php -// Example mutation resolver - class Logout { - public function __invoke($rootValue, array $args, GraphQLContext $context, ResolveInfo $resolveInfo) + /** + * @param null $_ + * @param array $args + */ + public function __invoke($_, array $args): ?User { - $guard = Auth::guard(); - - // When using Laravel Sanctum, always use Sanctum's guard to destroy a session: - // $guard = Auth::guard(config('sanctum.guard', 'web')); + // Plain Laravel: Auth::guard() + // Laravel sanctum: Auth::guard(config('sanctum.guard', 'web')) + $guard = ?; + /** @var \App\Models\User|null $user */ + $user = $guard->user(); $guard->logout(); - return !$guard->check(); + + return $user; } } - ``` - -If you are using [Laravel Sanctum](https://laravel.com/docs/master/sanctum), you should use Sanctum's guard in your mutation resolvers -to handle sessions. Sanctum supports both session-based and token-based authentication. The example implementations above -only make sense when using session-based authentication. From 063fdff874d0683adc1f97e4a6874e1b01acab80 Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Fri, 17 Jul 2020 13:26:12 +0200 Subject: [PATCH 7/7] Sanctum --- docs/master/security/authentication.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/master/security/authentication.md b/docs/master/security/authentication.md index a22a8abf87..6c11a54dca 100644 --- a/docs/master/security/authentication.md +++ b/docs/master/security/authentication.md @@ -162,7 +162,7 @@ class Login public function __invoke($_, array $args): User { // Plain Laravel: Auth::guard() - // Laravel sanctum: Auth::guard(config('sanctum.guard', 'web')) + // Laravel Sanctum: Auth::guard(config('sanctum.guard', 'web')) $guard = ?; if( ! $guard->attempt($args)) { @@ -191,7 +191,7 @@ class Logout public function __invoke($_, array $args): ?User { // Plain Laravel: Auth::guard() - // Laravel sanctum: Auth::guard(config('sanctum.guard', 'web')) + // Laravel Sanctum: Auth::guard(config('sanctum.guard', 'web')) $guard = ?; /** @var \App\Models\User|null $user */