Laravel Sanctum / Angular 2 xsrf-cookie with HttpClient

Im trying to consume a service in a Laravel (Backend) with Angular 2. I can doit without any problem if I doit with axios, but I need to doit with HttpClient

How can I translate this code:

const url = `${this.endPoint}/login`;
const urlCsrf = `${this.endPoint}/csrf-cookie`;
const body = { email, password };

axios.defaults.withCredentials = true;

axios.get(urlCsrf).then(() => {
    axios.post(url, body).then((resp) => {
        console.log(resp);
    });
});

to something like this but that it works:

//this code dont work returns csrf error
this.http.get(urlCsrf).subscribe(() => {
     this.http.post(url, body).subscribe((resp) => {
         console.log(resp);
    });
});

Upvotes: 0

Views: 616

Answers (3)

nicpas
nicpas

Reputation: 101

Maybe I'm late, but I west a lot of time to find the solution for this issue and I want to share you the final solution that was correct for me.

My setup: Angular 15 - Laravel 10 with sanctum.

Laravel configuration: .env file: add lines SESSION_DRIVER=cookie SESSION_DOMAIN=localhost

sanctum.php file add stateful localhost:4200 (for angular ng serve)

'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
    '%s%s',
    'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1,localhost:4200',
    Sanctum::currentApplicationUrlWithPort()
))),

cors.php set true for credentials:

 'supports_credentials' => true,

RESTART THE SERVER LARAVEL FOR APPLY CHANGES.

Then, in Angular app:

  1. Define interceptor like:

    export class HttpXsrfInterceptor implements HttpInterceptor { headerName = 'X-XSRF-TOKEN'; constructor(private tokenService: HttpXsrfTokenExtractor) {}

intercept( req: HttpRequest, next: HttpHandler ): Observable<HttpEvent> { req = req.clone({ withCredentials: true, });

req.headers.set('withCredentials', 'true');
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);

} }

  1. In AuthService, put the login method like:

    login(loginForm: LoginForm): Observable {

    return this.http.get('http://localhost/sanctum/csrf-cookie').pipe( switchMap(() => {

     return this.http
       .post<LoginResponse>(base_url + 'access/login', loginForm)
       .pipe(
         tap((data) => {
    
           const user = new User(
             data.user.id,
             data.user.firstname,
             data.user.lastname,
             data.user.username,
             data.user.email,
             data.user.created_at
           );
           this.user.next(user);
         })
       )
    

    }), catchError(error => { console.error(error); return throwError(error); }) );

};

I hope this help someone.

Upvotes: 0

eko
eko

Reputation: 40677

That code translates to:

const body = {email, password}
this.http.get(urlCsrf).subscribe(() => {
     this.http.post(url, body, { withCredentials: true }).subscribe((resp) => {
         console.log(resp);
    });
});

If you want to write it in a more reactive way:

const body = {email, password}
this.http.get(urlCsrf).pipe(
     switchMap(()=> this.http.post(url, body, { withCredentials: true })
  ).subscribe((resp) => console.log(resp));

If you want to apply the withCredentials to all requests, you should take a look at the interceptors as mentioned in the comments. A quick example:

@Injectable()
export class CredentialsInterceptor implements HttpInterceptor {
  constructor() {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
      return next.handle(req.clone({ withCredentials: true }));
  }
}

and then import it in your AppModule

providers: [
  { provide: HTTP_INTERCEPTORS, useClass: EngagementXSRFInterceptor, multi: true },
]

Upvotes: 1

Themodmin
Themodmin

Reputation: 455

I have ran to the same issue recently here is what worked for me:

$data = 'client_id=xxxxxxxxx&client_secret=xxxxxxxxx&grant_type=client_credentials';
$url = "https://xxxxxxxxxxxxxxxxxxxxx.x/xx/xxxxx";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLINFO_HEADER_OUT, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
    'Content-Type: application/x-www-form-urlencoded',
    'Content-Length: ' . strlen($data))
);
$contents = curl_exec($ch);
if (curl_errno($ch)) {
  echo curl_error($ch);
  echo "\n<br />";
  $contents = '';
} else {
  curl_close($ch);
}

if (!is_string($contents) || !strlen($contents)) {
echo "Failed to get contents.";
$contents = '';
}
$json = json_encode($contents);

Upvotes: 1

Related Questions