laravel / sanctum

Laravel Sanctum provides a featherweight authentication system for SPAs and simple APIs.
https://laravel.com/docs/sanctum
MIT License
2.75k stars 294 forks source link

CSRF token mismatch and Unauthenticated #41

Closed mbougarne closed 4 years ago

mbougarne commented 4 years ago

I can't get it to work with Nuxt in the front-end, firstly I got the 419 error number when I tried to access to /login which is a CSRF token issue, I disabled the CSRF token by adding wildcard access in VerifyCsrfToken Middleware:

protected $except = [
        '/*',
    ];

I passed the login part with that, but I faced another one which is 401 ~ Unauthenticated: Although I'm in the stateful mode

Laravel app is running on: http://localhost:8000/ Nuxt app is running on: http://localhost:3000/

I think, there's an issue on `EnsureFrontendRequestsAreStateful My Request using Axios as Nuxt Module:

// I get the cookie [I'm using api as a prefix]
this.$axios.$get('http://localhost:8000/api/csrf-cookie')
// I pass the login
.then(res => {
  this.$axios.$post('http://localhost:8000/login',
  {
    email: this.email,
    password: this.password
  }) 
  // I fail here with 401
  .then( data => {
    this.$axios.$get('http://localhost:8000/api/posts')
    .then( posts => console.log(posts))
  })
})
danpastori commented 4 years ago

Hi @mbougarne ! I've been trying to solve the same thing. If you visit the thread here: #11 I have all of my steps laid out as well. I think it's something with how Axios sends the cookie.

mbougarne commented 4 years ago

Hello @danpastori , It's not an Axios issue, I tried with vanilla JS using XHR, besides I disabled the CSRF on all routes and I got 401. I start to believe that Airlock assumes that the incoming requests are within Laravel application not another provider which is Vue/Nuxt in our case. So the SPA is just the front-end part that lives within Resources folder of Laravel App, and if we want to work with Nuxt, Vue-CLI or any front-end as a seperate app we should use API Tokens Not stateful

driesvints commented 4 years ago

I'll leave this to @taylorotwell to investigate.

taylorotwell commented 4 years ago

No. Airlock does not require you to put everything in the same app. I've tested it fine with Vue CLI. These are all CORS issues.

mbougarne commented 4 years ago

I don't think so, I used laravel-cors

monsterdream commented 4 years ago

@mbougarne agree, that same situation here.

danpastori commented 4 years ago

I got to this point yesterday and part of it was CORS. However if you are to the point where you are getting a valid 401 response, try changing the SESSION_DRIVER to cookie (mentioned in #11). A combination of correct CORS, SESSION_DOMAIN, and SESSION_DRIVER got this resolved.

I managed to get this working with the NuxtJS auth module as well and pushed the code to help out.

Here's my NuxtJS frontend: https://github.com/serversideup/airlock-nuxt-frontend Here's my Laravel Airlock Backend: https://github.com/serversideup/airlock-laravel-api

I wrote a quick guide to getting them working together focused mainly on NuxtJS frontend with their first class auth module: https://serversideup.net/using-laravel-airlock-with-nuxtjs/.

If you guys need any help, let me know!

SeinopSys commented 4 years ago

I found that I needed the following middleware to get any form of working CSRF with the current instructions as written.

Don't try this at home

Reading the token from the cookie header like the middleware above does will not protect against CSRF since that cookie is sent along with the request regardless of where it came from, defeating the purpose of CSRF protection entirely.

In order for this to work properly the SPA would need to send back the value of the XSRF-TOKEN cookie under the request header X-XSRF-TOKEN, which currently does not seem to be documented here, but this is how Laravel resolves encrypted CSRF cookies.

billisonline commented 4 years ago

@SeinopSys your solution worked for me, thanks!

SeinopSys commented 4 years ago

@billisonline I sincerely hope that you meant appending the header via JS, otherwise, by adding that middleware in your codebase, you are effectively making CSRF protection pointless. I've edited my previous comment to make this clearer nonetheless.

wannymiarelli commented 4 years ago

@SeinopSys Yes, this is what I actually did with fetch. The Laravel doc is clear about how to send back the CSRF token but yes, I think that would be a good idea to add some kind of reference in the readme.

// Check for CSRF token
let csrf = RegExp('XSRF-TOKEN[^;]+').exec(document.cookie)
csrf = decodeURIComponent(csrf ? csrf.toString().replace(/^[^=]+./, '') : '')

if (csrf) {
   headers.append('X-XSRF-TOKEN', csrf)
}
steks89 commented 4 years ago

@danpastori solution's worked for me SESSION_DRIVER=cookie (maybe is obvious for some people, but I think it could be in the airlock's documentation), Also people need to clear cookies before every test.

ouhaohan8023 commented 4 years ago

I find another reason. If you use Api Token rather than SPA. Your app/Http/Kernel.php file should look like this

'api' => [
//            useless for api token
//            EnsureFrontendRequestsAreStateful::class,
       'throttle:60,1',
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
 ],
paprotsky commented 4 years ago

AIRLOCK_STATEFUL_DOMAINS=127.0.0.1 in the .env file works for me.

tnduc commented 4 years ago

@taylorotwell Can you support me

POST: /api/curent-user: message: "Unauthenticated."


My config: SESSION_DRIVER=cookie

SESSION_DOMAIN=.codeky.local

AIRLOCK_STATEFUL_DOMAINS=admin.codeky.local

'supports_credentials' => true

'paths' => ['api/*', 'login'],

DeepinScreenshot_select-area_20200320182335 DeepinScreenshot_select-area_20200320182318 DeepinScreenshot_select-area_20200320182344

patrikengborg commented 4 years ago

I had this problem with getting an "Unauthenticated" error (401) for subsequent requests after a successful login. In my case it was because I made some API requests in nuxtServerInit or in the created hook. Because of how Nuxt works, those requests are made from the server and not from the client. And I guess the appropriate headers is not included then by default. I found two different solutions.

Make sure the request is made only from the client by using:

if (process.browser) {
  // Make request
}

This can be used in the created hook, but won't work in nuxtServerInit.

The other solution is to set proxyHeaders: true in the axios options. According to the docs:

In SSR context, this options sets client requests headers as default headers for the axios requests. This is useful for making requests which need cookie based auth on server side. This also helps making consistent requests in both SSR and Client Side code.

I hope this helps someone. I was banging my head for a while, before I figured out what was going on.

@taylorotwell @driesvints I think you would spare yourself a lot of support requests if you added a note about this trap in the docs. Many users seem to be stuck because of this, and think this is a problem with Sanctum, which it's not. Nuxt and Laravel seems to be a popular combo, and it would be a shame if they gave up on using Sanctum because of this.

FallenNoir commented 4 years ago

I'm not sure if this is relevant to your particular case, but I encountered a similar issue. I was receiving 419 while accessing /login straight after successful token request (sanctum/csrf-cookie). In my case the problem was that Auth::routes() were in web.php, rather than api.php. Moving the Auth::routes() to api.php fixed the issue to me.

incon commented 4 years ago

@FallenNoir You're required to send back the cookie XSRF-TOKEN as a HTTP header X-XSRF-TOKEN in the next request.

xtrasmal commented 4 years ago

For who has tried this all, followed the instructions etc, see if you are explicitly adding Accept and Content-type headers to every request. For example in custom Middleware. If so, change the priority of the middleware or don't add those headers for this request.

kakajansh commented 4 years ago

After trying all of the possible solutions, there is what I come up with, and a bit long checklist for future devs experiencing 401 Unauthorized and 419 Token mismatch erros.

Firstly, we should set both apps on same domain. We can use localhost for both, or if we use valet then we can configure reverse proxy for our nuxt app. If you prefer to use localhost, then launch backend app with php artisan serve and your backend will be available at localhost:8000 and frontend at localhost:3000.

I will go with valet, so backend will be blog.test and frontend at front.blog.test

Valet setup

Create blog.conf file at ~/.config/valet/Nginx/

map $sent_http_content_type $expires {
    "text/html"                 epoch;
    "text/html; charset=utf-8"  epoch;
    default                     off;
}

server {
    listen          127.0.0.1:80;
    server_name     front.blog.test;    # setup your domain here

    gzip            on;
    gzip_types      text/plain application/xml text/css application/javascript;
    gzip_min_length 1000;

    location / {
        expires $expires;

        proxy_redirect                      off;
        proxy_set_header Host               $host;
        proxy_set_header X-Real-IP          $remote_addr;
        proxy_set_header X-Forwarded-For    $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto  $scheme;
        proxy_read_timeout                  1m;
        proxy_connect_timeout               1m;
        proxy_pass                          http://127.0.0.1:3000; # set the adress of the Node.js instance here
    }
}

Backend

/config/auth.php In my app, I moved from JWT to Sanctum. So need to recheck auth file. If you created fresh app then skip this.

'defaults' => [
    'guard' => 'web',
    'passwords' => 'users',
],

'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'users',
    ],
    'api' => [
        'driver' => 'token',
        'provider' => 'users',
        'hash' => false,
    ],
],

/config/sanctum.php

'stateful' => [
    'front.blog.test'
],

/config/cors.php

'paths' => [
    'sanctum/csrf-cookie',
    'login',
    'api/*'
],
'allowed_methods' => ['*'],
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => true,

/config/session.php

'driver' => 'cookie',
'domain' => '.blog.test',
'same_site' => 'lax',

/app/Http/Kernel.php Make sure you have cors

protected $middleware = [
    ...
    \Fruitcake\Cors\HandleCors::class,
];

/routes/api.php Make sure you use auth:sanctum middleware

<?php
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
    return $request->user();
});

Frontend

nuxt.config.js

modules: [
  '@nuxtjs/axios',
  '@nuxtjs/auth'
],

axios: {
  baseURL: "http://blog.test",
  credentials: true   // Attention, credentials not withCredentials
},

auth: {
  redirect: {
    login: '/login',
    logout: '/',
    callback: '/login',
    home: '/'
  },
  strategies: {
    local: {
      endpoints: {
        login: { url: '/login', method: 'post', propertyName: false },
        user: { url: '/api/user', method: 'get', propertyName: false }
      },
      tokenRequired: false,
      tokenType: false
    }
  },
  localStorage: false
},

Using Laravel Sanctum/Airlock with NuxtJS Authentication in Nuxt.js using Laravel Sanctum

ianrussel commented 4 years ago

I also have 419 issue.My react app lives inside rerources.How do you confiigure the sanctum stateful ? my app is laravel-app.test. my backend api is in laravel-app.test/admin/v1/ and the react is in laravel-app.test/admin . I tried what the docs says in sanctum but no luck.

Rialga commented 4 years ago

I find another reason. If you use Api Token rather than SPA. Your app/Http/Kernel.php file should look like this

'api' => [
//            useless for api token
//            EnsureFrontendRequestsAreStateful::class,
       'throttle:60,1',
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
 ],

its work for me, thanks !

damdiaz commented 4 years ago

Configuragtion

In .env file

...
CLIENT_URL=client.test.localhost
SESSION_DOMAIN=.test.localhost
...

config/sanctum.php

'stateful' => [
    env('CLIENT_URL')
],

config/cors.php

   'paths' => [
        'api/*',
        'sanctum/csrf-cookie',
        'login',
        'logout'
    ],

    'allowed_methods' => ['*'],

    'allowed_origins' => [env('CLIENT_URL')],

    ...

    'allowed_headers' => ['*'],

    ...

    'supports_credentials' => true

Backend Application

app/Http/Kernel.php

   protected $middleware = [
        ...
        \Fruitcake\Cors\HandleCors::class,
        ...
    ];
   protected $middlewareGroups = [
        ...
        'api' => [
            EnsureFrontendRequestsAreStateful::class,
            'throttle:60,1',
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],
    ];

routes/web.php

...
Route::post('login', 'Auth\LoginController@login')->name('login');
...

routes/api.php

...
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
    return $request->user();
});
...

Client Application

.env

API_URL=api.test.localhost

nuxt.config.js

...
  modules: [
    '@nuxtjs/axios',
    '@nuxtjs/auth-next'
  ],
...
  auth: {
    strategies: {
      cookie: {
        cookie: {
          name: 'X-XSRF-TOKEN'
        }
      },
      laravelSanctum: {
        provider: 'laravel/sanctum',
        url: process.env.API_URL
      }
  }

The name for the cookie must be set to X-XSRF-TOKEN and not XSRF-TOKEN

ryoyin commented 3 years ago

I find another reason. If you use Api Token rather than SPA. Your app/Http/Kernel.php file should look like this

'api' => [
//            useless for api token
//            EnsureFrontendRequestsAreStateful::class,
       'throttle:60,1',
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
 ],

you saved my day. I tried 3 hours to make it work.

andreladocruz commented 3 years ago

We run our applications in google app engine with 2 different services: default (laravel) and myorders (nuxt)

MyOrders services call the laravel API and we use sanctum.

You can try it here: https://dev.digitalmanager.guru/myorders/

When the user fills the email and click to send, we call the https://dev.digitalmanager.guru/sanctum/csrf-cookie URL and the cookie is corrected created:

image

After we send a post request to send the user email and got a token mismatch error:

image

Even with the X-XSRF-TOKEN header correctly sent:

image

@taylorotwell, can you help us find the solution?

andreladocruz commented 3 years ago

@taylorotwell,

Digging deeper I found the reason for the token mismatch error.

When using redis sessions, the getTokenFromRequest from Illuminate\Foundation\Http\Middleware\VerifyCsrfToken returns something like this "30995|DDjAe5opsd7urpwrpwJLEvlFePuLmvEdXHNlr00e";" while the $request->session()->token() returns something like this "DDjAe5opsd7urpwrpwJLEvlFePuLmvEdXHNlr00e".

So, the command "hash_equals($request->session()->token(), $token)" returns false.

I added the following block of code to App\Http\Middleware\VerifyCsrfToken and it worked

image

alignwebs commented 3 years ago

If anyone is facing CSRF token mismatch issue, then please try checking if SANCTUM_STATEFUL_DOMAINS= is set in .env

When SANCTUM_STATEFUL_DOMAINS is not present in ENV then it throws CSRF token mismatch error.

Hope it helps someone. :)

kennyuzoma commented 3 years ago

@alignwebs wow this actually worked. thanks

ToNyRANDRIAMANANTSOA commented 3 years ago

For my case, the cause of the CSRF token mismatch was that I have multiple host names (other than localhost) pointing to 127.0.0.1 set on my local machine, and reactscripts started the development server to the other hostname. Changing it back to localhost solved the issue, they should be on the same domain :)

benounnas commented 2 years ago

it may seem obvious, but as developers we learn from mistakes to improve. like @ToNyRANDRIAMANANTSOA mentioned, i had a smiliar issue where the frontend client was pointing to http://127.0.0.1:8000 rather than http://localhost:8000 where my SESSION_DOMAIN=localhost and SANCTUM_STATEFUL_DOMAINS=localhost:8086 (quasar). thank you !

walteranyika commented 1 year ago

I encountered the same issue when using localhost domain, but when I switched to a valet domain like app.test, everything worked perfectly.

peshr4w commented 1 year ago

I found that I needed the following middleware to get any form of working CSRF with the current instructions as written.

Don't try this at home

Reading the token from the cookie header like the middleware above does will not protect against CSRF since that cookie is sent along with the request regardless of where it came from, defeating the purpose of CSRF protection entirely.

In order for this to work properly the SPA would need to send back the value of the XSRF-TOKEN cookie under the request header X-XSRF-TOKEN, which currently does not seem to be documented here, but this is how Laravel resolves encrypted CSRF cookies.

where is this file?

victorsiqueira14 commented 11 months ago

If you are using JWT. I commented this line in the kernel and it worked!

Screenshot 2023-11-08 at 10 01 36

ArifulSikder commented 11 months ago

I found that I needed the following middleware to get any form of working CSRF with the current instructions as written. Don't try this at home Reading the token from the cookie header like the middleware above does will not protect against CSRF since that cookie is sent along with the request regardless of where it came from, defeating the purpose of CSRF protection entirely. In order for this to work properly the SPA would need to send back the value of the XSRF-TOKEN cookie under the request header X-XSRF-TOKEN, which currently does not seem to be documented here, but this is how Laravel resolves encrypted CSRF cookies.

where is this file?

Is this process secure?