Till now I was storing access token in local storage. Now I want to move it into cookies.
So I added \Laravel\Passport\Http\Middleware\CreateFreshApiToken::class, into Kernel.php. I also changed my axios call:
axios.get('/forum', {
params: {
filterBy: vm.filterBy,
filterDirection: vm.filterDirection,
theme: vm.theme
},
withCredentials: true,
headers: {
//'Authorization': 'Bearer ' + this.getToken,
'X-Requested-With': 'XMLHttpRequest',
'X-CSRF-TOKEN':window.Laravel.csrfToken
}
}).then(function (response) {
vm.posts=response.data
}).catch(function (error) {});
You can see that I commented Authorization header where I was sending access token from local storage. I see that CSRF token is added in request header and that access token is saved in X-XSRF-TOKEN. There is also a Cookie with value: XSRF-TOKEN: (token value).
Now I am getting error with: Route[login] not defined.
Perhaps problem is that in Cookie value there is XSRF-TOKEN instead of laravel_token ?
Login api call
axios.post('/login', {
username: credentials.username,
password: credentials.password,
}).then(response => {
const token = response.data.access_token
localStorage.setItem('access_token', token)
localStorage.setItem('username', credentials.username)
resolve(response)
}).catch(error => {
reject(error)
})
And on back-end I then create token with:
$response = $http->post(config('services.passport.login_endpoint'), [
'form_params' => [
'grant_type' => 'password',
'client_id' => config('services.passport.client_id'),
'client_secret' => config('services.passport.client_secret'),
'username' => $request->username,
'password' => $request->password,
]
]);
return json_decode((string) $response->getBody(), true);
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 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 implementing JWT token for my laravel 6 project. I have followed this tutorial and successfully configured the application to use JWT. I am also able to login using auth()->attempt($credentials) which returns the JWT token.
Now when I try to access any other api route and send jwt token in request header for authorization, it always returns 401 (Unauthorized).
Here is my code
api.php
Route::group([
'prefix' => 'auth'
], function ($router) {
Route::post('login', 'Auth\React\RAuthController#login');
Route::post('get_user_pages', 'Auth\React\RAuthController#getUserPages');
});
axios request
const response = await axios.post('http://127.0.0.1:8000/api/auth/get_user_pages', {}, {
headers: {"Authorization": `Bearer ${cookie.load('token')}`}
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
Controller function
public function getUserPages()
{
$auth_user_id = auth()->id();
$user_role_id = User::find($auth_user_id)->roles->makeHidden('pivot')->pluck('id')->first();
return $user_pages = ROLE::find($user_role_id)->pages->toArray();
}
I don't understand when I am able to access login route then why not other routes?
Try editing the route
Route::group([
'middleware' => 'api',
'prefix' => 'auth'
], function ($router) {
Route::post('login', 'Auth\React\RAuthController#login');
Route::post('get_user_pages', 'Auth\React\RAuthController#getUserPages');
});
Insideconfig/auth.php file:
'guards' => [
'api' => [
'driver' => 'jwt',
'provider' => 'users',
],
],
I'm trying to create a bearer token for passport so I can use my api routes internally. I send a post request to /oauth/token with the client id and client secret. The code that generates the bearer token looks like this.
public function generateToken() {
try {
$req = Request::create('/oauth/token', 'POST', [
'grant_type' => 'client_credentials',
'client_id' => '1',
'client_secret' => 'POe81tKvmZ0DhBdtwdM7VPoV5tTKobVYzSc9A0RX',
'scope' => '*',
]);
$res = app()->handle($req);
return $res;
} catch (Exception $e){
log::critical('Failed to generate bearer token.');
} catch (error $e) {
log::critical('Failed to generate bearer token.');
}
}
However when trying to do this request I get a 401 authorised.
#content: "{"error":"invalid_client","message":"Client authentication failed"}" #version: "1.1"#statusCode: 401 #statusText: "Unauthorized"
Is there anyway of fixing this?
This works for me
$client = new client(['verify' => false]);
$response = $client->post($url, [
'form_params' => [
'client_id' => 5,
// The secret generated when you ran: php artisan passport:install
'client_secret' => 'HxLB5LVi3HhHxYkoHVZinoYUz5Ab3HnbWmiB1KBd',
'grant_type' => 'client_credentials',
'scope' => '*',
]
]);
Apparently, am able to consume my own API via Javascript using VueJs' Axios and jQuery's Ajax but the same has failed to work with Guzzle HTTP client.
How can I use the CreateFreshApiToken middleware with Guzzle.
Axios - Ok
axios.get('api/user').then(response => {
console.log(response.data);
}).catch(error => {
console.log(error.response.data);
});
Ajax - OK
window.$.ajax({
headers: {
'Accept': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
},
type: 'GET',
url: 'api/user',
success: function (response) {
console.log(response);
},
error: function (xhr) {
console.error(xhr.responseText);
}
});
Guzzle - Failed
try {
$client = new Client([
'base_uri' => 'http://localhost/passport-test/public/api/',
'headers' => [
'Accept' => 'application/json',
],
]);
$api_response = $client->request('GET', 'user', ['debug' => true]);
$user = json_decode($api_response->getBody(), true);
return response()->json($user);
} catch (ConnectException $ex) {
return response()->json(['code' => $ex->getCode(), 'message' => $ex->getMessage()]);
} catch (ClientException $ex) {
return json_decode($ex->getResponse()->getBody(), true);
} catch (ServerException $ex) {
return response()->json(['code' => $ex->getCode(), 'message' => $ex->getMessage()]);
}
Well, createFreshApiTokens attaches a cookie for authorization, but when you are using guzzle you are not making a request from the client side ( browser ) so the cookie is not attached to the request!