You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
We're having an issue using Lighthouse with Sanctum's SPA Authentication. It seems that if we pass a referer that is in the list of SANCTUM_STATEFUL_DOMAINS we get returned a CSRF Token Mismatch even if we are attempting to access a query that doesn't require authentication.
If we are actually authenticated and pass the referer and the correct auth info we do get back our requests successfully.
If we aren't authenticated and the referer is passed, the EnsureFrontendRequestsAreStateful seems to trigger a CSRF issue...even though no authentication has been provided or is required.
If we try to authenticate and then call a normal API route that is protected by the "auth:sanctum" middleware, debugging the request shows Sanctum arrives at an unauthenticated conclusion and then the response back is correct...in the case of Lighthouse, it seems like we're being thrown out at the EnsureFrontendRequestsAreStateful middleware state even though we aren't actually attempting to get data that requires authentication, or passing any data that would indicate we are authenticated.
Switching to token based authentication works fine...and in our current development state we're fine to do that in the short term, not sure if anyone else seems to be experiencing this?
I believe what should happen is that if we are accessing data that doesn't require authentication, the data should be returned, but it doesn't seem to make it that far.
Steps to reproduce
Trying to access a query that requires no authentication throws the CSRF token mismatch if not authenticated...if we then
Authenticate using a React app on one of Sanctum's allowed domains, we get an authenticated session...then
I would expect this to happen. Laravel middleware does not know anything about GraphQL and if fields that require authentication are requested.
The recommended approach is to use a middleware that optionally authenticates users, such as AttemptAuthentication.
EnsureFrontendRequestsAreStateful seems to require authentication for any request and does not allow it to be absent. You can try and create a version of it that optionally requires authentication. If you can come up with a nice solution, feel free to add a PR to Lighthouse or post it in this issue.
Sanctum first tries to use the web guard (or whatever is overridden in config('sanctum.guard')) to authenticate. This should probably be a session-based guard. If no session is present, Sanctum checks for attached tokens.
Adding EnsureFrontendRequestsAreStateful to Lighthouse's route middleware allows creating login / logout mutations that send the session cookie with its response and in turn, use existing cookies for auth. If you're using graphql-playground, make sure to add "request.credentials": "include" in the settings, otherwise cookies won't be transmitted alongside XHR. Make sure that your frontend XHR library (axios etc.) uses a similar setting.
This way, Lighthouse @auth and @guard directives work just fine.
EnsureFrontendRequestsAreStateful seems to require authentication for any request and does not allow it to be absent
@spawnia This is not the case. Unauthenticated requests are completely fine. I am still figuring out how to get CSRF back.
So, @junioro12, it seems that Sanctum always validates CSRF tokens, regardless of whether the requested resource requires authentication. Although the following applies…
You only need to protect "unsafe" requests. That is, actions on your application that change state. See RFC 7231 for details on safe requests. Unsafe requests are usually implemented via the POST method, so you should focus on protecting all POST requests rather than GET requests. This is assuming that all unsafe requests are POSTs (they should be if correctly implemented). https://security.stackexchange.com/a/85893
… this behaviour is still correct: GraphQL usually sends requests via POST. POST requests are, by HTTP definition (see section 9.2.1), non-idempotent and can change application state, thus we'd need CSRF protection. (GraphQL GET requests can contain mutations as well and thus are "unsafe" as well, although this is against the HTTP spec.) Authentication is a separate matter, since both authenticated and unauthenticated requests can be "unsafe" and thus are CSRF targets.
In our environment, we don't know whether the request is idempotent = "safe", up to the point where Lighthouse parses the request. We would need a separate Lighthouse stage to determine whether a request is "safe", probably out of scope for this library (@spawnia?) and maybe not even possible since model getters could technically write to the DB. This pre-processing stage could then run as a middleware before CSRF validation kicks in.
Since we don't live in the future yet, CSRF validation always runs before anything else happens. And that's where the story basically ends. ¯\(ツ)/¯
OK...thanks for the follow up...the "session" based authentication from the Sanctum package is nice, but I see the challenge here. For now we're just using a token instead, but the idea of Sanctum's SPA authentication had some attractive benefits.
It worked for me without adding graphql endpoint to $except in VerifyCsrfToken.php middleware.
Setup
Backend
// config/lighthouse.php
...
'middleware' => [
\Nuwave\Lighthouse\Support\Http\Middleware\AcceptJson::class,
// Logs in a user if they are authenticated. In contrast to Laravel's 'auth'
// middleware, this delegates auth and permission checks to the field level.
\Nuwave\Lighthouse\Support\Http\Middleware\AttemptAuthentication::class,
// Logs every incoming GraphQL query.
// \Nuwave\Lighthouse\Support\Http\Middleware\LogGraphQLQueries::class,
// Sanctum
Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
]
// app/Http/Middleware/VerifyCsrfToken.php
...
/**
* Indicates whether the XSRF-TOKEN cookie should be set on the response.
*
* @var bool
*/
protected $addHttpCookie = true;
/**
* The URIs that should be excluded from CSRF verification.
*
* @var array
*/
protected $except = [];
// httpClient.js (or anywhere you initialize axios)
import axios from 'axios'
...
axios.defaults.withCredentials = true;
Don't need to add any XSRF / CSRF option as it is configured by default in axios config :
// AXIOS DOCUMENTATION
// `xsrfCookieName` is the name of the cookie to use as a value for xsrf token
xsrfCookieName: 'XSRF-TOKEN', // default
// `xsrfHeaderName` is the name of the http header that carries the xsrf token value
xsrfHeaderName: 'X-XSRF-TOKEN', // default
Using Apollo Client (apollo-client) for graphql client :
You have to get the csrf token from where you execute your graphql request, and add it to the headers request as X-CSRF-TOKEN name.
1. Getting the csrf from app
If it is the same origin app, it is given by Laravel by csrf_token() helper in blade files.
Example :
I too received a CSRF Token Mismatch response when using
Lighthouse GraphQL
Laravel Sanctum
Session Authentication
Apollo (Frontend Client)
Our setup was that
Ask for a CSRF cookie from /sanctum/csrf-cookie
Make a axios request to api/login with a X-XSRF-TOKEN header to login and create an authenticated session
From now on, only use Apollo GraphQL client to make the requests (CRUD). We use the same method to retrieve the CSRF token from the cookie and add it to the Apollo GraphQL headers (X-XSRF-TOKEN)
What happened here after some digging is that the CSRF token we receive in the cookie from sanctum is encoded.
Axios by default decodes the CSRF value, but somehow not Apollo.
What did the fix here was to decode the CSRF value ourselves, meaning:
constcsrfToken=decodeURIComponent(getCookie("XSRF-TOKEN"));consthttpLink=createHttpLink({uri: constants.BASE_URL+"graphql",headers: {"X-XSRF-TOKEN": csrfToken,// Note: Dont put decodeURIComponent here as it won't work },credentials: "include",});
Here, decodeURIComponent is a browser native function that decodes the token for us.
That is what initially fixed the CSRF token mismatch.
Extra: We simply used the config suggestions mentioned in this issue, aka
Activity
spawnia commentedon Jun 7, 2020
I would expect this to happen. Laravel middleware does not know anything about GraphQL and if fields that require authentication are requested.
The recommended approach is to use a middleware that optionally authenticates users, such as
AttemptAuthentication.EnsureFrontendRequestsAreStatefulseems to require authentication for any request and does not allow it to be absent. You can try and create a version of it that optionally requires authentication. If you can come up with a nice solution, feel free to add a PR to Lighthouse or post it in this issue.dlindenkreuz commentedon Jun 24, 2020
I'm dealing with the same issue and – CSRF protection aside – solved it like this:
Sanctum first tries to use the web guard (or whatever is overridden in
config('sanctum.guard')) to authenticate. This should probably be a session-based guard. If no session is present, Sanctum checks for attached tokens.Adding
EnsureFrontendRequestsAreStatefulto Lighthouse's route middleware allows creating login / logout mutations that send the session cookie with its response and in turn, use existing cookies for auth. If you're using graphql-playground, make sure to add"request.credentials": "include"in the settings, otherwise cookies won't be transmitted alongside XHR. Make sure that your frontend XHR library (axios etc.) uses a similar setting.This way, Lighthouse
@authand@guarddirectives work just fine.@spawnia This is not the case. Unauthenticated requests are completely fine. I am still figuring out how to get CSRF back.
Edit: Boiled the fix down to the essentials.
spawnia commentedon Jun 24, 2020
@dlindenkreuz can you write up the proposed solution in https://github.com/nuwave/lighthouse/blob/master/docs/master/security/authentication.md?
We can iterate on the details in a PR.
dlindenkreuz commentedon Jun 24, 2020
Yup, gonna do tomorrow
dlindenkreuz commentedon Jun 25, 2020
So, @junioro12, it seems that Sanctum always validates CSRF tokens, regardless of whether the requested resource requires authentication. Although the following applies…
… this behaviour is still correct: GraphQL usually sends requests via POST. POST requests are, by HTTP definition (see section 9.2.1), non-idempotent and can change application state, thus we'd need CSRF protection. (GraphQL GET requests can contain mutations as well and thus are "unsafe" as well, although this is against the HTTP spec.) Authentication is a separate matter, since both authenticated and unauthenticated requests can be "unsafe" and thus are CSRF targets.
In our environment, we don't know whether the request is idempotent = "safe", up to the point where Lighthouse parses the request. We would need a separate Lighthouse stage to determine whether a request is "safe", probably out of scope for this library (@spawnia?) and maybe not even possible since model getters could technically write to the DB. This pre-processing stage could then run as a middleware before CSRF validation kicks in.
Since we don't live in the future yet, CSRF validation always runs before anything else happens. And that's where the story basically ends. ¯\(ツ)/¯
junioro12 commentedon Jun 25, 2020
OK...thanks for the follow up...the "session" based authentication from the Sanctum package is nice, but I see the challenge here. For now we're just using a token instead, but the idea of Sanctum's SPA authentication had some attractive benefits.
dlindenkreuz commentedon Jun 26, 2020
@spawnia check this out #1458
spawnia commentedon Aug 5, 2020
The docs were improved and the example project has a working implementation.
ne0t3ric commentedon Nov 9, 2020
It worked for me without adding graphql endpoint to
$exceptinVerifyCsrfToken.phpmiddleware.Setup
Backend
Front-end
Don't need to add any XSRF / CSRF option as it is configured by default in axios config :
You have to get the csrf token from where you execute your graphql request, and add it to the headers request as X-CSRF-TOKEN name.
1. Getting the csrf from app
If it is the same origin app, it is given by Laravel by csrf_token() helper in blade files.
Example :
So in one of your javascript file :
2. Add to graphql client header
Hope it helps
PenguinDetective commentedon Dec 10, 2021
I too received a CSRF Token Mismatch response when using
Our setup was that
/sanctum/csrf-cookieapi/loginwith aX-XSRF-TOKENheader to login and create an authenticated sessionX-XSRF-TOKEN)What happened here after some digging is that the CSRF token we receive in the cookie from sanctum is encoded.
Axios by default decodes the CSRF value, but somehow not Apollo.
What did the fix here was to decode the CSRF value ourselves, meaning:
Here, decodeURIComponent is a browser native function that decodes the token for us.
That is what initially fixed the CSRF token mismatch.
Extra: We simply used the config suggestions mentioned in this issue, aka