Advaith
Advaith

Reputation: 2590

CSRF token mismatch Laravel sanctum and Angular http

I have been trying to implement Laravel sanctum, but I am having this error "CSRF token mismatch" even though I followed everything that is said in the Laravel Sanctum documentation

cors.php config file

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

kernal is added as per the documentation, so not wasting space by adding its code here

.env file

SESSION_DRIVER=cookie
SESSION_DOMAIN=localhost
SANCTUM_STATEFUL_DOMAINS=localhost

I am using Angular 9 as my frontend here

This is my interceptor

request = request.clone({
    withCredentials: true
})

This is how I send the request to Laravel

this.http.get<any>(url('sanctum/csrf-cookie')).subscribe(() => {
     this.http.post<any>(url('login'), { this.username, this.password })
         .subscribe(success => console.log(success), error => console.log(error))
})

Once the first route is hit I can confirm the creation of cookies, but the issue is with the second route ('/login')

Upvotes: 12

Views: 25707

Answers (6)

The Blind Hawk
The Blind Hawk

Reputation: 1666

If you tried all other solutions but can't find the issue, make sure you have not edited the session table in any way.

My issue was this piece of code in the migrations

$table->foreignId('user_id')->constrained(
    table: 'users',
    indexName: 'user_id'
)->nullable();

nullable() must be ran before constrained() to take effect.
This caused Laravel to fail inserting new columns into the table since the first insert statements has user_id=null.

Adding to the fact that Laravel's error are silent when it fails writing into the sessions table, and this issue is not easy to spot.

Other issues such as the db_user's authorizations lacking will give the same error so make sure to check the "sessions" table and that it is storing the data as intended then debug from there.

Upvotes: 0

Serdar
Serdar

Reputation: 100

I have a similar problem in my case but only some users are having this problem. I solved my problem by changing SESSION_DOMAIN to .localhost in the session.php file under the config folder.

Upvotes: 2

Ahmadshoh
Ahmadshoh

Reputation: 79

In my case, this problem was solved in a strange way. I went to the Illuminate\Foundation\Http\Middleware\VerifyCsrfToken file and there on line 76 in the "handle" method inside the "if case" there was a line like this:

$this->tokensMatch($request);

For testing purposes, I returned this string

return response()->json($this->tokensMatch($request));

and surprisingly it will return a normal token. Then I returned everything to its place, and it is strange, but this error was no longer there.

Upvotes: 1

Muhammad Umair
Muhammad Umair

Reputation: 691

I was able to resolve this issue by adding http:// before localhost in config/sanctum.php

From this

'stateful' => explode(',', env(
        'SANCTUM_STATEFUL_DOMAINS',
        'localhost,127.0.0.1'
)),

To this

'stateful' => explode(',', env(
            'SANCTUM_STATEFUL_DOMAINS',
            'http://localhost,127.0.0.1'
    )),

Upvotes: 16

Ayenew Yihune Demeke
Ayenew Yihune Demeke

Reputation: 1277

My problem was that I was accessing the api on port 8001. It worked when I add it (127.0.0.1:8001) to 'stateful' in config/sanctum.php.

Upvotes: 1

CleanCode
CleanCode

Reputation: 384

You need to send x-csrf-token in the header, (Angular includes it automatically only in relative URLs not absolute)

You can create an interpreter to do this, something like this should work:

import {Injectable} from '@angular/core';
import {
  HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpXsrfTokenExtractor
} from '@angular/common/http';

import { Observable } from 'rxjs';

@Injectable()
export class HttpXsrfInterceptor implements HttpInterceptor {
  headerName = 'X-XSRF-TOKEN';

  constructor(private tokenService: HttpXsrfTokenExtractor) {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

    if (req.method === 'GET' || req.method === 'HEAD') {
      return next.handle(req);
    }

    const token = this.tokenService.getToken();

    // Be careful not to overwrite an existing header of the same name.
    if (token !== null && !req.headers.has(this.headerName)) {
      req = req.clone({headers: req.headers.set(this.headerName, token)});
    }
    return next.handle(req);
  }
}

Upvotes: 7

Related Questions