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
Related
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
I am trying to build an API using Laravel 8 and Sanctum Authentication. I know that they are many similar questions but still haven't found any working solution.
In my situation I don't use Vue or Angular, I give directly the auth token to the users and they need to call the API endpoints to get the data.
Here is my routes/api.php:
Route::group(['middleware' => ['auth:sanctum'], 'as' => 'api.'], function () {
Route::get('polls/random', [PollController::class, 'random'])->name('polls.random');
Route::get('polls/vote', [PollController::class, 'vote'])->name('polls.vote');
});
Route::fallback(function() {
throw new PageNotFoundException();
});
config/cors.php
'paths' => ['api/*', 'sanctum/csrf-cookie'],
'allowed_methods' => ['*'],
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => true,
config/sanctum.php
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
'%s%s',
'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
env('APP_URL') ? ','.parse_url(env('APP_URL'), PHP_URL_HOST) : ''
))),
On localhost I am using virtual hosts, so here is my .env file:
SANCTUM_STATEFUL_DOMAINS=beteu,domain.com
'api' => [
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
Here is how I try to call the API:
$client = new Client(['base_uri' => $this->api_url]);
$access_token = $this->api_token;
$headers = [
'Authorization' => 'Bearer '.$access_token,
'Accept' => 'application/json',
];
try {
$response = $client->request('GET', $this->path, [
'headers' => $headers
]);
}
catch (\Exception $exception) {
echo $exception->getMessage();
\Log::info($exception->getMessage());
exit();
}
I have tried a lot of the possible solutions here but always get the 401 Unauthenticated Error.
Much appreciate your help!
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)
)),
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
I am a novice with Lumen and have recently integrated dusterio/lumen-passport via composer into my project. Following a tutorial I have successfully created authentication for 'client' instances, so I am able to send variables
grant_type: client_credentials
client_id: {my id}
client_secret: {my secret}
to /oauth/token and get a bearer token. That is working great.
What I need to be able to do, and I cannot find sufficient documentation anywhere, is to create user login functionality. This is so that I can hook a UI up to the Lumen API and users be able to enter their email address and password to get access. If any one has any information to help me achieve this I would be extremely grateful. Below are edits I have made to set up the passport process...
bootstrap/app.php
$app->routeMiddleware([
'client.credentials' => Laravel\Passport\Http\Middleware\CheckClientCredentials::class,
]);
$app->register(App\Providers\AuthServiceProvider::class);
$app->register(Laravel\Passport\PassportServiceProvider::class);
$app->register(Dusterio\LumenPassport\PassportServiceProvider::class);
config/auth.php
'defaults' => [
'guard' => env('AUTH_GUARD', 'api'),
'passwords' => 'users'
],
'guards' => [
'api' => [
'driver' => 'passport',
'provider' => 'users'
],
],
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => \App\User::class
]
],
routes/web.php
$router->group(['middleware' => 'client.credentials'], function () use ($router) {
$router->get('/test', 'TestController#index');
});
The way I did it with my laravel based client (seperate apps) was to save the token to a cookie which gets called each request using middleware to authenticate the request heres my code.
<?php
namespace App\Http\Controllers;
use GuzzleHttp\Client;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cookie;
class AuthController extends Controller {
public function __construct()
{
}
public function showLoginPage()
{
return view('Login');
}
public function attemptLogin(Request $request)
{
$client_id = env('CLIENT_ID');
$client_secret = env('CLIENT_SECRET');
$username = $request->input('email');
$password = $request->input('password');
$guzzle = new Client;
$response = $guzzle->post('https://api.domain.com/oauth/token', [
'form_params' => [
'grant_type' => 'password',
'client_id' => $client_id,
'client_secret' => $client_secret,
'username' => $username,
'password' => $password,
'scope' => '*',
],
]);
$reply = json_decode($response->getBody(), true);
$token = $reply['access_token'];
return redirect('/')->cookie('token', $token);
}
public function attemptLogout(Request $request)
{
$accessToken = $request->cookie('token');
$client = new Client(['base_uri' => 'https://api.domain.com']);
$headers = [
'Authorization' => 'Bearer ' . $accessToken,
'Accept' => 'application/json',
];
$response = $client->request('GET', 'logout', [
'headers' => $headers
]);
$status = $response->getStatusCode();
if($status === 200)
{
return redirect('/login')->withCookie(Cookie::forget('token'))->with('success','Logout Successful');
} else {
return response('API Logout Failed', 500);
}
}
}