Nuxt-Laravel-Sanctum CSRF token mismatch 419 error - laravel

I have a Nuxt-Laravel-Sanctum CSRF token mismatch 419 error while Laravel is hosted on a server and Nuxt is on localhost on a PC. I have uploaded my Laravel project for getting API on api.repairtofix.com.
And I am trying to log in from localhost in my pc from Nuxt. While clicking on the login button I get the following error.
{message: "CSRF token mismatch.", exception:
"Symfony\Component\HttpKernel\Exception\HttpException",…}
Login method
login() {
this.$auth.loginWith('laravelSanctum', {
data: this.form
})
.then(response => console.log(response))
.catch(error => console.log(response))
}
.env
APP_URL=http://api.repairtofix.com
SESSION_DOMAIN=api.repairtofix.com
SANCTUM_STATEFUL_DOMAINS=.repairtofix.com,localhost:3000
Kernel.php
'api' => [
EnsureFrontendRequestsAreStateful::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
sanctum.php
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS',
'api.repairtofix.com,localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1'
)),
cors.php
'paths' => ['api/*', 'sanctum/csrf-cookie', 'login', 'signup', 'getUser'],
'allowed_methods' => ['*'],
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => true,
api.php
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
});
// register
Route::get('register', function(Request $request){
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => bcrypt($request->password)
]);
return $user;
});
// login
Route::post('login', function(Request $request){
$credentials = $request->only('email', 'password');
if(!auth()->attempt($credentials)){
throw ValidationException::withMessages([
'email' => 'Invalid credentials'
]);
}
$request->session()->regenerate();
return response()->json(null, 201);
});
// logout
Route::post('logout', function(Request $request){
auth()->guard('web')->logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return response()->json(null, 201);
});
nuxt.config.js
modules: [
'#nuxtjs/axios',
'#nuxtjs/pwa',
'#nuxtjs/auth-next',
'#nuxtjs/toast',
],
auth:{
strategies: {
'laravelSanctum': {
provider: 'laravel/sanctum',
url: 'http://api.repairtofix.com',
endpoints: {
login: {
url: '/api/login'
},
logout: {
url: '/api/logout'
},
user: {
url: '/api/user'
},
},
user: {
property: false
}
},
},
redirect: {
login: "/login",
logout: "/",
home: "/"
}
},

I guess you are using SPA authentication with sanctum, both your server and client has to be on the same domain. The client(localhost) and your api is on different domain.
docs
In order to authenticate, your SPA and API must share the same top-level domain. However, they may be placed on different subdomains.

In my case, Axios credentials were not set to true in nuxt.config file, also the text is case sensitive
axios: {
baseUrl: 'http://localhost:8000',
credentials: true,
},

It is maybe a little bit late but others in future.
1-check the api/config/sanctum in laravel and make the ports correct on localhost:xxxx and 127.0.0.1:xxxx
2-on .env file make coorection for SANCTUM_STATEFUL_DOMAINS=localhost:xxxx

Add the below statement to your .env file and clear the cache.
AIRLOCK_STATEFUL_DOMAINS=127.0.0.1

Related

Access-Control-Allow-Origin is missing laravel-9 and vuejs3

I have a vue3-frontend and a laravel9-backend. I want to send data to the backend using the fetch-api in vue and get the response but unfortunately I get this error message when I click the submit button. Does anyone have an idea how I can solve the problem ?
I tried the fruitcake/laravel-cors library but it still did not work
error from firefox de tool](https://i.stack.imgur.com/SCcqL.png)
<?php
return [
/*
|--------------------------------------------------------------------------
| Cross-Origin Resource Sharing (CORS) Configuration
|--------------------------------------------------------------------------
|
| Here you may configure your settings for cross-origin resource sharing
| or "CORS". This determines what cross-origin operations may execute
| in web browsers. You are free to adjust these settings as needed.
|
| To learn more: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
|
*/
'paths' => ['api/*', 'sanctum/csrf-cookie'],
'allowed_methods' => ['*'],
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => false,
];
vuejs-code
methods: {
registerUser() {
fetch("http://localhost:8000/api/tippspiel/register", {
method: "POST",
body: JSON.stringify({
email: this.email,
username: this.username,
password: this.password,
passwordConfirmation: this.passwordConfirmation,
}),
headers: {
'Content-type': 'application/json; charset=UTF-8',
},
}).then((response) => {
return response.json();
}).then((data) => {
console.log(data);
})
}
}
controlelr code laravel
public function register(Request $request)
{
$data = $request->validate([
"email" => "required|email",
"username" => "required|min:6|max:15",
"password" => "required|min:12|max:40",
"passwordConfirmation" => "required|same:password",
"ageCheck" => "required|accepted",
"privacyCheck" => "required|accepted"
]);
return [
"status" => "ok",
"id" => 1,
"username" => $request->username
];
}
Thanks:)
Create new middleware called Cors, inside of it, put this code:
public function handle($request, Closure $next)
{
$headers = [
'Access-Control-Allow-Origin' => "*",
'Access-Control-Allow-Methods' => 'POST, GET, OPTIONS',
'Access-Control-Allow-Headers' => 'Content-Type, X-Auth-Token, Origin',
];
if ($request->getMethod() == "OPTIONS") {
return \Response::make('OK', 200, $headers);
}
$response = $next($request);
foreach ($headers as $key => $value)
$response->header($key, $value);
return $response;
}
Go to App\Http\Kernel.php, in the protected $middleware array, add this:
\App\Http\Middleware\Cors::class,
and inside protected $routeMiddleware array, add this:
'cors' => \App\Http\Middleware\Cors::class,
In your Route file, add this:
Route::middleware(['cors'])->group(function(){
//your route here
});
Run this command inside your laravel folder:
php artisan optimize:clear

Laravel sanctum with react same domain 401 authentication error using cookies

Authenticating with the sanctum token is working just fine but sadly I can't get it to work using cookies instead (calling /api/user returns 401 Unauthorized). I'd really like to make use of the cookie authentication because it's safer.
Does anyone has an idea of what I'm doing wrong (or missing) to make the authentication work based on cookie so the frontend doesn't have to send sanctums bearer token?
Setup
I'm running React outside of Laravel but both the laravel backend and the react frontend are running on the same domain under subdomains (api.website.com and customer.website.com).
.env
SESSION_DRIVER=cookie
SESSION_DOMAIN=.website.com
SESSION_LIFETIME=120
SESSION_SECURE_COOKIE=false
SANCTUM_STATEFUL_DOMAINS=localhost:8000,.website.com
kernel.php
'api' => [
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
session.php
'same_site' => 'lax',
'http_only' => true,
'driver' => env('SESSION_DRIVER', 'file'),
'cookie' => env(
'SESSION_COOKIE',
Str::slug(env('APP_NAME', 'laravel'), '_').'_session'
),
cors.php
'paths' => ['api/*', 'sanctum/csrf-cookie'],
'allowed_methods' => ['*'],
'allowed_origins' => ['http://localhost:3000', 'https://api.website.com', 'https://customer.website.com'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['x-csrf-token', 'x-xsrf-token', 'content-type'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => true,
UserController.php
public function login(Request $request)
{
if (!$request->email) {
return response()->json(['error' => 'Missing email'], 401);
} elseif (!$request->password) {
return response()->json(['error' => 'Missing password'], 401);
} elseif (!$request->device_name) {
return response()->json(['error' => 'Missing device_name'], 401);
} else {
$credentials = $request->validate([
'email' => ['required', 'email'],
'password' => ['required'],
]);
if (Auth::attempt($credentials)) {
$auth = Auth::user();
$token = $auth->createToken($request->device_name)->plainTextToken;
$auth->setAttribute('token', $token);
return response()->json($token, 200);
}
return response()->json(['error' => 'The provided credentials do not match our records.'], 401);
}
}
Api.php
// Force all API responses to be in JSON format.
Route::middleware('json.response')->group(function () {
// UNAUTHENTICATED API's.
Route::post('login', [UserController::class, 'login']);
// AUTHENTICATED API's.
Route::middleware('auth:sanctum')->group(function () {
// USER.
Route::get('/user', function (Request $request) {
return $request->user();
});
// ADMIN.
Route::middleware('role.admin')->group(function () {
Route::get('/migrate', function () {
artisan::call('migrate');
return response()->json("success", 200);
});
Route::get('/route/clear', function () {
artisan::call('route:clear');
return response()->json("success", 200);
});
});
});
});
Frontend
const instance = axios.create({
withCredentials: true,
headers: {
Accept: 'application/json',
},
});
const loginWithEmailPasswordAsync = async (email, password) => {
// eslint-disable-next-line no-return-await
return await instance
.post('https://api.website.com/api/login', {
email,
password,
device_name: '_device',
})
.then((res) => {
return instance
.get('https://api.website.com/api/user')
.then((user) => {
return user;
})
.catch((error) => error);
})
.catch((error) => error);
};
try {
instance
.get('https://api.website.com/sanctum/csrf-cookie')
.catch((error) => error);
const loginUser = yield call(loginWithEmailPasswordAsync, email, password);....
Request and response headers when the 401 Unauthorized error pops up
Not sure if it matters but in my browser network tab I do see what seems to be the correct request headers such as the filled in X-XSRF-TOKEN, Cookie and in the response headers the set-cookie = laravel_session=ySiTVYYvyFKHSt9Q0VnP2vl4xeIee0MnjvlfQSu4; expires=Tue, 02-Aug-2022 18:55:13 GMT; Max-Age=7200; path=/; domain=.website.com; httponly; samesite=lax
I think you need to change SANCTUM_STATEFUL_DOMAINS from this
SANCTUM_STATEFUL_DOMAINS=localhost:8000,.website.com
to this:
SANCTUM_STATEFUL_DOMAINS=localhost:8000,customer.website.com
Since you'll have different .env for your dev and production environment, I think you should remove localhost host totally.
You can change your code from
SANCTUM_STATEFUL_DOMAINS=localhost:8000,.website.com
To
SANCTUM_STATEFUL_DOMAINS=website.com,frontend-subdomain-if-any.website.com

Nuxt.js / Sanctum : $auth.user still empty

Little question: my authentication with Sanctum has issues...
My login fonction:
const response = await axios.get(`${process.env.apiUrl}/sanctum/csrf-cookie`)
console.log(response)
const res = await this.$auth.loginWith('laravelSanctum', {
data: {
email: this.userLogin.login.email,
password: this.userLogin.login.password,
}
})
console.log(res);
console.log(this.$auth)
this.$router.push('/mon-compte/mon-espace');
In local:
The first consoleLog is ok, API CSRF Cookie is OK.
The second consoleLog is ok too, API Login is OK.
The third consoleLog is ok, $auth.user contain all informations returned by API Login.
3 APIs are called : csrf-cookie / login / user.
I'm connected.
But in Staging (prod dev):
The first consoleLog is ok, API CSRF Cookie is OK.
The second consoleLog is ok too, API Login is OK.
The third consoleLog is not ok, $auth.user is empty.
Only 2 APIs are called: csrf-cookie / login.
I don't understand...
My nuxt.config.js:
auth: {
strategies: {
'laravelSanctum': {
provider: 'laravel/sanctum',
url: process.env.apiUrl,
endpoints:{
login: {
url: '/api/login',
method: 'post',
propertyName: 'access_token'
},
user: {
url: '/api/user',
method: 'get',
propertyName: false
},
logout: {
url: '/api/logout',
method: 'post'
},
},
register: {
url: '/api/register',
method: 'post'
}
}
},
redirect: {
login: '/mon-compte/login',
logout: '/mon-compte/login',
home: '/mon-compte/mon-espace',
register: '/mon-compte/register'
}
},
And on Laravel, api.php:
Route::apiResource('user', UserController::class);
Route::post('/login', [AuthController::class, 'login']);
Route::post('/logout', [AuthController::class, 'logout']);
Route::post('/register', [AuthController::class, 'register']);
Route::post('/me', [AuthController::class, 'me']);
And my authController:
public function register(Request $request)
{
$validateData = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|min:8'
]);
$user = User::create([
'email' => $validateData['email'],
'name' => $validateData['name'],
'type' => 'user',
'password' => bcrypt($validateData['password'])
]);
$token = $user->createToken('auth_token')->plainTextToken;
return response()->json([
'access_token' => $token,
'token_type' => 'Bearer'
]);
}
public function login(Request $request)
{
if (!Auth::attempt($request->only('email', 'password'))) {
return response()->json([
'message' => 'Login information is invalid.'
], 401);
}
$user = User::where('email', $request['email'])->firstOrFail();
$token = $user->createToken('authToken')->plainTextToken;
return response()->json([
'access_token' => $token,
'token_type' => 'Bearer',
'user' => $user
]);
}
public function me(Request $request)
{
return response()->json(['data' => $request->user(), "result" => true]);
}
public function logout(Request $request){
Auth::logout();
auth()->guard('sanctum')->logout();
//auth('sanctum')->logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return response()->json(['message' => 'Client successfully signed out']);
}
Any ideas?
What i'm doing wrong?
Thanks a lot!
I believe you don't have a user endpoint to fetch the user after login, because nuxt-auth will do an api request to the endpoint you set in your nuxt.config.js which fethes the logged in user.
And for CSRF token you don't need to make the request the nuxt-auth will take care of that.
public function me(Request $request)
{
return response()->json(['data' => $request->user(), "result" => true]);
}

How do I fix the "401 Unauthorized" error with Laravel 8 Sanctum and VueJS 3?

So I have this Laravel 8 project with VueJS 3 in the frontend, and I'm running it on my localhost
And I'm using Sanctum for my authentication.
The Login and Sign Up process works fine, but when I go to a dashboard and I try to add something to my database it gives me the 401 Unauthorized error.
Laravel:
EDIT: UserController
public function login(Request $request)
{
$credentials = [
'email' => $request->email,
'password' => $request->password,
];
if (Auth::attempt($credentials)) {
$success = true;
$user = User::where('email', $credentials['email'])->first();
$token = $user->createToken("authToken")->plainTextToken;
} else {
$success = false;
}
$response = [
'success' => $success,
'access_token' => $token,
];
return response()->json($response);
}
web.php
Route::get('/{any}', function () {
return view('welcome');
})->where('any', '.*');
api.php
// Auth
Route::post('login', [UserController::class, 'login']);
Route::post('register', [UserController::class, 'register']);
Route::post('logout', [UserController::class, 'logout'])->middleware('auth:sanctum');
// Niveau
Route::group(['prefix' => 'niveaux', 'middleware' => 'auth:sanctum'], function () {
Route::get('/', [NiveauScolaireController::class, 'index']);
Route::post('add', [NiveauScolaireController::class, 'add']);
Route::get('edit/{id}', [NiveauScolaireController::class, 'edit']);
Route::post('update/{id}', [NiveauScolaireController::class, 'update']);
Route::delete('delete/{id}', [NiveauScolaireController::class, 'delete']);
});
cors.php
'paths' => ['api/*', 'sanctum/csrf-cookie'],
'allowed_methods' => ['*'],
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => true,
sanctum.php
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
'SANCTUM_STATEFUL_DOMAINS',
'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
env('APP_URL') ? ',' . parse_url(env('APP_URL'), PHP_URL_HOST) : ''
))),
session.php
'domain' => env('SESSION_DOMAIN', null),
So that's what I set up for the Laravel side.
VueJS
app.js
import { createApp } from "vue";
import App from "./App.vue";
import router from "./Router/index";
import axios from "axios";
const app = createApp(App);
app.config.globalProperties.$axios = axios;
app.use(router);
app.mount("#app");
bootsrap.js
window.axios = require("axios");
window.axios.defaults.headers.common["X-Requested-With"] = "XMLHttpRequest";
windoow.axios.defaults.withCredentials = true;
Login component
this.$axios.get("/sanctum/csrf-cookie").then((res) => {
this.$axios
.post("api/login", {
email: this.email,
password: this.password,
})
.then((res) => {
if (res.data.success) {
this.$router.push({ name: "Dashboard" });
} else {
console.log("Error: " + res.data.message);
}
})
.catch(function (error) {
console.log("error :>> ", error);
});
});
Here my request for sanctum/csrf-cookie is going well and I’ll receive Cookies, the login works fine, it gets the user from the database, and then it redirects me to the dashboard.
Problem
Now here when I try to send a request to (api/niveaux/add) the request is sent, but I get a 401 Unauthorized error with a {"message":"Unauthenticated."} response.
Dashboard component
this.$axios
.post("api/niveaux/add", {
nom: this.niveau,
})
.then((res) => {
if (res.data.success) {
alert(res.data.message);
} else {
alert(res.data.message);
}
})
.catch(function (error) {
console.log("error :>> ", error);
});
you need to send you access_token in every request that has middleware('auth:sanctum') in laravel.
you can find access_token inside the cookie you get after logging in. (might have different name like token)
axios.defaults.headers.common['Authorization'] = `Bearer ${access_token}`
or
axios.defaults.headers.common['Authorization'] = `${access_token}`
gotta do the trick
I think you are accessing through localhost port 8000, but in your stateful param under sanctum config, there is no localhost:8000. The config uses $_SERVER['SERVER_NAME'] so it actually looks for the exact content while accessing it.
sanctum.php
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
'SANCTUM_STATEFUL_DOMAINS',
'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
'localhost:8000',
env('APP_URL') ? ',' . parse_url(env('APP_URL'), PHP_URL_HOST) : ''
))),
noticed a typo here:
window.axios = require("axios");
window.axios.defaults.headers.common["X-Requested-With"] = "XMLHttpRequest";
windoow.axios.defaults.withCredentials = true;
the last "windoow" was spelt wrong.
When I experienced this, I traced and compared changes to an existing working project
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
'SANCTUM_STATEFUL_DOMAINS',
'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
env('APP_URL') ? ',' . parse_url(env('APP_URL'), PHP_URL_HOST) : ''
))),
in config/sanctum.php had been changed, I just replaced it with the old code
'stateful' => explode(',', env(
'SANCTUM_STATEFUL_DOMAINS',
'localhost,127.0.0.1,127.0.0.1:8000,::1,' . parse_url(env('APP_URL'), PHP_URL_HOST)
)),

Nuxt-Laravel-Sanctum User unauthenticated after login OR Error: "Route [login] not defined"

I am using Nuxt-Laravel-Sanctum.
While hosting on localhost for both api and client, login attempt is success and user response is getting back.
But, after uploading to server (api: api.repairtofix.com & client: admin-control.repairtofix.com) login seems to be success while user details is not getting back. I get error with 401 {message: "Unauthenticated."}
ie. It works on npm run dev, but after npm run generate it doesn't work
nuxt.config.js
axios: {
credentials: true
},
auth: {
strategies: {
'laravelSanctum': {
provider: 'laravel/sanctum',
url: 'https://api.repairtofix.com',
endpoints: {
login: {
url: '/api/login',
},
},
}
}
},
LoginController.php
public function __invoke(Request $request)
{
$credentials = $request->only('email', 'password');
if(!auth()->attempt($credentials)){
throw AuthenticationException();
}
}
api.php
use App\Http\Controllers\Auth\LoginController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
});
Route::post('/login', LoginController::class);
cors.php
"Accept"=>"application/json",
'paths' => ['api/*', '/user', 'sanctum/csrf-cookie'],
'allowed_methods' => ['*'],
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => true,
.env
SESSION_DOMAIN=.repairtofix.com
SANTUM_STATEFUL_DOMAINS=admin-control.repairtofix.com
Login.vue
data() {
return {
login: {
email: 'nmg.gta#gmail.com',
password: 'password'
}
}
},
methods: {
async userLogin() {
try {
let response = await this.$auth.loginWith('laravelSanctum', { data: this.login })
console.log(response)
} catch (err) {
console.log(err)
}
}
}
I found the solution
In sanctum.php file I forgot to add my domains. After adding I got the resopnse from /user

Resources