I'm setting up a Vue js/Vuetify website with a PHP Slim Framework API, and tuuopla slim-jwt-auth as a middleware for JWT token authentication.
The unprotected routes are working fine but when I try to send axios requests to the protected routes in the API I only got token not found error.
I dont know if the problem is at Vue js, axios or the API configuration. curl and Postman gives the decoded key as expected when acessing the protected route, only the Vue js website gives this errors.
To run the API I'm using PHP built-in server: `php -S localhost:8000 -t public/
In any case, the localStorage.getItem("token") does exist, as I tried to print them before every request as well in the interceptor.
Here is a test component :
<template>
<v-btn #click="test">Test</v-btn>
<v-btn #click="test2">Test</v-btn>
</template>
<script>
methods: {
test() {
axios
.post("api/user",{},{
headers: {
Authorization: `Bearer ${localStorage.getItem("token")}`
}
}
)
.then(res => console.log(res))
.catch(err => console.log(err));
},
test2() {
var yourConfig = {
headers: {
Authorization: "Bearer " + localStorage.getItem("token")
}
};
axios
.get("test", yourConfig)
.then(res => console.log(res))
.catch(err => console.log(err));
}
},
</script>
axios config(tried with and without the interceptor)
axios.defaults.baseURL = "http://localhost:8000";
axios.interceptors.request.use(
config => {
let token = localStorage.getItem("token");
if (token) {
config.headers["Authorization"] = `Bearer ${token}`;
}
console.log(token)
return config;
},
error => {
return Promise.reject(error);
}
);
Slim index.php(a protected and a unprotected sample routes for my tests)
...
use Slim\Http\Request;
use Slim\Http\Response;
$app->group('/api', function (\Slim\App $app) {
$app->get('/user', function (Request $request, Response $response, array $args) {
return $response->withJson($request->getAttribute('decoded_token_data'));
});
});
$app->get('/test', function (Request $request, Response $response, array $args) {
return $response->withJson(["hi"=>"hello"]);
});
// Run app
$app->run();
middleware.php(tried many configurations)
<?php
// Application middleware
use Slim\Http\Request;
use Slim\Http\Response;
use Monolog\Logger;
use Monolog\Handler\RotatingFileHandler;
$logger = new Logger("slim");
$rotating = new RotatingFileHandler(__DIR__ . "/logs/slim.log", 0, Logger::DEBUG);
$logger->pushHandler($rotating);
$app->add(new \Tuupola\Middleware\JwtAuthentication([
"secure" => false,
"logger" => $logger,
"relaxed" => ["localhost:8080"],
"attribute" => "decoded_token_data",
"secret" => "mykey",
"algorithm" => ["HS256"],
"rules" => [
new \Tuupola\Middleware\JwtAuthentication\RequestPathRule([
// Degenerate access to '/api'
"path" => ["/api"],
// It allows access to 'login' without a token
"passthrough" => [
"/login_admin"
//"/login_admin"
]
])
],
"error" => function ($response, $arguments) {
$data["status"] = "error";
$data["message"] = $arguments["message"];
return $response
->withHeader("Content-Type", "application/json")
->write(json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
}
]));
The errors when tried to access the api/user route:
Chrome console:
OPTIONS http://localhost:8000/api/user net::ERR_ABORTED 401 (Unauthorized)
Access to XMLHttpRequest at 'http://localhost:8000/api/user' from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
API Response:
{
"status": "error",
"message": "Token not found."
}
did you try adding
RewriteRule .* - [env=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
to your .htaccess file?
Related
I'm trying to get some data from a Laravel API endpoint but I'm getting some very unusual redirect issues and disallowed methods. I will start by showing my javascript code first, then laravel code second.
First, I ran the following javascript code from the developer console of various websites with various top level domains to ensure I can authenticate without any CORS issues:
fetch("https://api.example.com/login",{
method:"post",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({"email":"test#test.com","password":"test","remember_me":true})
})
.then(r=>r.json())
.then(r=>console.log(r));
// I get a perfect response like this:
{ data: { message: "Yay! Success!", token: "86|S5isCezrsYb1aToAsI3xZb9Ot9Tu7WU8XeOK1q8C" } }
I then take the token and do another get request from my developer console to the /auth/user end point to get my account details like this
fetch("https://api.example.com/auth/user",{
method:"get",
header:{
Authorization:"Bearer 86|S5isCezrsYb1aToAsI3xZb9Ot9Tu7WU8XeOK1q8C",
Accept: "application/json"
},
})
.then(r=>r.json())
.then(r=>console.log(r));
But something completely weird happens. The https://api.example.com/auth/user gives my developer console a 302 response. Then my developer console automatically (without any intervention from me) makes a GET request to https://api.example.com/login. The https://api.example.com/login then gives a 405 response with this message:
Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException: The GET method is not supported for this route. Supported methods: POST. in file /var/www/api/vendor/laravel/framework/src/Illuminate/Routing/AbstractRouteCollection.php on line 117
#0 /var/www/api/vendor/laravel/framework/src/Illuminate/Routing/AbstractRouteCollection.php(103): Illuminate\Routing\AbstractRouteCollection->methodNotAllowed(Array, 'GET')
#1 /var/www/api/vendor/laravel/framework/src/Illuminate/Routing/AbstractRouteCollection.php(40): Illuminate\Routing\AbstractRouteCollection->getRouteForMethods(Object(Illuminate\Http\Request), Array)
#2 /var/www/api/vendor/laravel/framework/src/Illuminate/Routing/RouteCollection.php(162):
...etc...
This is my Laravel code:
~/routes/api.php
Route::post('login', ['as' => 'login', 'uses' => 'Api\UserController#login']); //->middleware(['throttle:6,1']);
Route::middleware('auth:sanctum')->get('/auth/user', function (Request $request) {
return $request->user();
});
~/app/Http/Controller/Api/UserController.php
public function login(LoginRequest $request)
{
$user = User::where('email', $request->email)->first();
if (!$user || !Hash::check($request->password, $user->password)) {
throw ValidationException::withMessages([
'message' => ['The provided credentials are incorrect.'],
]);
exit;
} else if (!$user->active) {
throw ValidationException::withMessages([
'message' => ['Your account approval is pending.'],
]);
exit;
}
return response()->json([
'data' => [
"message" => __("Yay! Success!"),
"token" => $user->createToken('authentication-token')->plainTextToken,
],
]);
}
So my question is, why can't the /auth/user endpoint return information about the user I'm logged in with?
EDIT
I tried php artisan route:clear which had no effect.
I tried changing the route from /auth/user to authuser in both the route/api.php and my fetch() call, and suddenly I have a CORS error. Is the auth/ path a reserved word of some kind?
Oh...i guess this config/cors.php also matters:
'paths' => [
'api/*',
'auth/user',
'login',
'logout',
'sanctum/csrf-cookie',
],
UPDATE
I created a few more routes like this:
~/routes/api.php
Route::middleware(['auth:sanctum'])->group(function () {
Route::resource('blah1', Api\Blah1::class)->only(['index', 'store', 'show', 'update', 'destroy']);
Route::resource('blah2', Api\Blah1::class)->only(['index', 'store', 'show', 'update', 'destroy']);
// .. etc...
Route::get('/auth/user', function (Request $request) {
return $request->user();
});
});
~/config/cors.php
'paths' => [
'api/*',
'auth/user',
'blah*',
'login',
'logout',
'sanctum/csrf-cookie',
],
I noticed that POSTMAN has ZERO problems getting information from auth/user and blah1, blah2, etc...
However, it is just the browser that will always give a 302 and cause a redirect when pinging auth/user, blah1, blah2, etc.... FireFox shows me these responses:
If I remove the middleware('auth:sanctum'), then I no longer get the 405 and 302 issue. However, $request->user() becomes null without the middleware('auth:sanctum'). I need a way to get the user information from Authorization: Bearer <token>
I am building a ReactJS App with a Laravel 8.9.0 backend api. I am using the Laravel Auth functionality that creates a token and passes it to my front end app. I am able to log-in and create a token properly with a hash password etc. What I am not able to do is "Check Login" with the is_login method shown below in the controller. The Auth::check() is always failing, what am I doing wrong? Below are my controllers and routes api.php file. Please help!
Login Controller:
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Models\Users;
class LoginController extends Controller
{
public function login(Request $request) {
$login = $request->validate([
'email' => 'required:string',
'password' => 'required:string'
]);
if(!Auth::attempt($login)) {
return response([
'message' => 'Invalid Credentials'
]);
}
$accessToken = Auth::user()
->createToken('authToken')
->accessToken;
return response([
'user' => Auth::user(),
'access_token' => $accessToken
]);
}
public function is_login()
{
$is_login = false;
if (Auth::check()) {
$is_login = true;
}
return response()->json(['is_login' => $is_login]);
}
}
Routes api.php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
Route::post('/login', 'App\Http\Controllers\LoginController#login');
Route::get('/login-check', 'App\Http\Controllers\LoginController#is_login');
To fix this problem you must pass the token into the header with every request:
In my ReactJS I did this on componentDidMount()
componentDidMount() {
try {
const config = {
headers: {
Authorization: `Bearer ${mytoken}`
}
};
axios.get('/api/login-check',
{ key: "value" },
{ headers: config }
).then(
console.log('succ')
).catch(
console.log('err')
);
} catch (err) {
console.log('Error in submission', err)
}
}
and in my api I added the middleware to the check:
Route::middleware('auth:api')->get('/login-check', 'App\Http\Controllers\LoginController#is_login');
Good Day Can someone help me in posting array of data using guzzle, i've followed the documentation guzzle documentation and i dont know what am missing.
Routes: sync.php
$api->version('v1', [
'prefix' => 'api/v1',
],
$api->group([
'prefix' => 'sync'
], function ($api) {
$api->post('/accounts', 'App\Http\Controllers\SyncController#sync_accounts');
$api->get('/updateaccount', 'App\Http\Controllers\SyncController#updateaccounts');
});
]);
Controller: SyncController
use GuzzleHttp\Client;
use Illuminate\Http\Request;
public function updateaccounts()
{
$data = array('listid' => 'ListID',
'Name'=> 'Name',
'parentname'=> 'ParentRefFullName',
'fullname'=> 'FullName');
$http_call = new Client(['base_uri' => URL_CLOUD]);
$res = $http_call->post('sync/accounts/', [json_encode($data)]);
dd($res);
}
public function sync_accounts(Request $patch, $id)
{
$data = $patch->getContent();
return $data;
}
my problem here is
"error": {
"message": "Client error: POST http://.../api/v1/sync/accounts/ resulted in a 405 Method Not Allowed response:\n{\"error\":{\"message\":\"405 Method Not Allowed\",\"status_code\":405}}\n",
"code": 405,
"status_code": 500
}
Http 405 means that you are firing the wrong request to that endpoint get -> post or posting to get.
Your url seems to have a v1 which is not defined as prefix so if u try this url instead:
baseURL/sync/accounts
I have an issue with logging out using JWT package. On Angular side I am removing the token from local storage and calling Laravel API:
logout(): void {
this.cacheHandler.clearCache();
return this.http.get(environment.apiUrl + 'logout')
.toPromise()
.then(response => {
const responseData = response.json();
return responseData.success;
})
.catch(error => {
return error;
});
}
And on Laravel side:
public function logout()
{
try {
JWTAuth::invalidate(JWTAuth::getToken());
return response()->json(['success' => true, 'message' => 'Logout successful'], 200);
} catch (JWTException $e) {
return response()->json(['success' => false, 'error' => 'Failed to logout, please try again.'], 500);
}
}
API method is protected with JWT auth middleware, so when calling API I am forwarding Authorization Bearer token which was stored in local storage. Method passes, token is valid.
After that it enters the logout() method and I am getting the response that logout was successful.
Problem is that I can trigger the same request, with same token, always getting the same message. If it was invalidated, it couldn't trigger the second request because it is behind a middleware.
Also, with the same token, which was supposed to be invalidated, I can call other API methods which require authentication (without the token they don't work)
try using the new JWTGuard and call it like this:
public function logout()
{
$this->guard()->logout(); // pass true to blacklist forever
}
That logout functions calls the following code:
public function logout($forceForever = false)
{
$this->requireToken()->invalidate($forceForever);
$this->user = null;
$this->jwt->unsetToken();
}
Trying to get the header authorization key in controller for making an API. Request is making from fiddler.
$headers = apache_request_headers();
And the $header contains an array.
Array
(
[User-Agent] => Fiddler
[Host] => localhost:8000
[Content-Length] => 102
[Authorization] => TestKey
)
If am trying like this to fetch the Authorization , its throwing error.
$header['Authorization]
Error :
Undefined index: Authorization
Tried many ways to get the authorization, but not working anything. Is there any way to fetch this?
To get headers from the request you should use the Request class
public function yourControllerFunction(\Illuminate\Http\Request $request)
{
$header = $request->header('Authorization');
// do some stuff
}
See https://laravel.com/api/5.5/Illuminate/Http/Request.html#method_header
Though it's an old topic, it might be useful for somebody...
In new Laravel versions, it's possible to get bearer Authorization token directly by calling Illuminate\Http\Request's bearerToken() method:
Auth::viaRequest('costom-token', function (Request $request) {
$token = $request->bearerToken();
// ...
});
Or directly from a controller:
public function index(Request $request) {
Log::info($request->bearerToken());
// ...
}
If you use a specific package like "JWT" or "sanctum" you can use their own middleware to retrieve user information.
Also, Laravel provides many ways to get the authorization key like :
$request->bearerToken(); to get only token without 'Bearer' word the result will be like 44|9rJp2TWvTTpWy535S1Rq2DF0AEmYbEotwydkYCZ3.
$request->header('Authorization'); to get the full key like Bearer 44|9rJp2TWvTTpWy535S1Rq2DF0AEmYbEotwydkYCZ3
P.S. you can use request() shortcut instead of using $request variable
You can try install the jwt(JSON Web Token Authentication for Laravel & Lumen) http://jwt-auth.com via composer.
And create a middleware that verify if exists yours token key in the header request.
After to install jwt, Your middleware it could be as follows
<?php
namespace App\Http\Middleware;
use Closure;
use JWTAuth;
use Tymon\JWTAuth\Exceptions\JWTException;
use Tymon\JWTAuth\Exceptions\TokenExpiredException;
use Tymon\JWTAuth\Exceptions\TokenInvalidException;
class VerifyJWTToken
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
try {
$user = JWTAuth::toUser($request->header('token'));
} catch (JWTException $e) {
if ($e instanceof TokenExpiredException) {
return response()->json([
'error' => 'token_expired',
'code' => $e->getStatusCode()
], $e->getStatusCode());
}
else if($e instanceof TokenInvalidException){
return response()->json([
'error' => "token_invalid",
'code' => $e->getStatusCode()
], $e->getStatusCode());
}
else {
return response()->json([
'error' => 'Token is required',
'code' => $e->getStatusCode(),
], $e->getStatusCode());
}
}
return $next($request);
}
}
I used token for example for a header key, but you can name it, as you like.
Then you could use this on any controller
There is a simple way to get headers in any file or controller with $_SERVER.
print_r($_SERVER); // check your header here
then, you can simply get your header:
$AuthToken = $_SERVER['HTTP_AUTHORIZATION'];