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',
],
],
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 have a Laravel 8 project based on auth bootstrap scaffolding.
It uses a custom UserProvider that authenticates a user from another source (AWS user pool). The login process works just fine. However, subsequent Ajax calls to a route fail with error 401. Below are are the details.
Server side (config/auth.php)
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
'hash' => false,
],
],
'providers' => [
'users' => [
'driver' => 'aws',
'model' => App\Models\User::class,
],
]
Providers/AuthServiceProvider.php
public function boot()
{
$this->registerPolicies();
Auth::provider('aws', function ($app, array $config) {
$awsProvider = new AwsUserProvider($app['hash'], $config['model']);
return $awsProvider;
});
}
routes/web.php
Route::get('ict-devices-table', [IctDataController::class, 'getAllDevices']);
Controllers/IctDataController.php
class IctDataController extends Controller
{
public function __construct()
{
$this->middleware('auth');
}
public function getAllDevices()
{
// return json encoded data.
}
}
Client side
As I mentioned, the login is successful, and the user gets redirected to the home page. On that page, the ajax request executes periodically to get the latest data from the 'ict-devices-table' That request returns error 401.
$.ajaxSetup({
headers: {
'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content')
}
});
$.ajax({
'url': 'http://localhost:8000/ict-devices-table',
'method': 'GET',
'contentType': 'application/json',
}).done(function (inData) {
//do something useful with the data.
});
The problem is that the controller never executes if auth middleware gets specified in the constructor. If it is not the getAllDevices method is called but Auth::check() returns false and Auth::user()is null.
However, one clue might be: The custom UserProvider class implements all UserProvider methods, and they get called during login, i.e., validateCredentials, retrieveById, etc. However, when the Ajax request gets made, none of those calls appear in the log. Any ideas why this happens and how to fix it?
I'm deploying a RestApi with Laravel 5.5 and using JWT ("tymon/jwt-auth": "dev-develop") for authentication, PostMan for test and and Php 7.3.3-1. I followed all steps from the oficial website (JWT Tymon).
It works fine when I try to login, even it return the error when password or email is wrong. But when I try to access to the content which I want to share on Api, It returns Session store not set on request.
This is my routes providers:
protected function mapResourcesv2()
{
Route::prefix('api')
->middleware('api')
->namespace($this->namespace)
->group(base_path('routes/resources.php'));
// Route::group([
// 'prefix' => 'api',
// 'middleware' => 'auth:api',
// 'namespace' => $this->namespace,
// ], function ($router) {
// require base_path('routes/resources.php');
// });
}
The line comments is the routes which Im using in all my files (a lot of routes isn't good idea having all routes in one file) Only the no-comm line works fine.
AuthController file is the same of the official website (Quick-start).
And this is my LoginController:
public function __construct(Request $request)
{
$this->middleware('guest', ['except' => ['logout', 'getLogout']]);
$this->request = $request;
}
Route::group(
[
'middleware' => 'api',
'prefix' => 'auth'
],
function($router)
{
Route::post('login', 'AuthController#login');
Route::post('logout', 'AuthController#logout');
Route::post('refresh', 'AuthController#refresh');
Route::post('me', 'AuthController#me');
Route::group(
[
'prefix' => 'product',
],
function()
{
Route::post('price/{id}', 'Resources\Entrypoint\Http\Laravel\Product\ProductGetController#getPrice');
Route::post('price', 'ProductPostController#changePrice');
}
);
}
);
This is the routes which returns the login page (both weren't edited, maybe should I edit something?)
Route::get('blackboard/login', 'Auth\LoginController#showBlackboardLogin');
Route::post('blackboard/login', 'Auth\LoginController#postAdminLogin');
LoginController file:
public function showBlackboardLogin(){
return view('blackboard/login');
}
public function postAdminLogin(Request $request){
$request->session()->put('login',Carbon::now()->toDateTimeString());
$conditions = [
'email' => $this->request->input('email'),
'password' => $this->request->input('password'),
'active' => 1,
];
if(Auth::attempt($conditions, $this->request->has('remember'))){
return redirect('blackboard');
}else{
//TODO, mensaje mal
return redirect('blackboard/login');
}
}
In postman I'm using "Authorization" tab and "type:Bearer Token" and pasted the token generated after login.
The user should have access to the routes from '/product'.
But it only works for login, if I try to access '/me' it redirect to login page (only login works and returns the error if password is wrong). Actually the error is Session store not set on request.
Laravel throw status code only over AJAX requests, so, if you are using rest clients as postman you need to pass X-Requested-With: XMLHttpRequest or Accept: application/json over headers.
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);