JWT Auth token not invalidating after logout in Lumen 5.4 - php

I had a fresh Lumen 5.4 installation and followed this tutorial. Login and others work fine but the logout doesn't seem to work properly. What I mean is, if I try to expire a token it doesn't give me an error but if the same token(the one that was just expired) is re-used, it should say expired but still goes through and gets me the data. In simple terms, I believe it is not expiring the token at all. Below is my code:
UserController code:
class UserController extends Controller
{
protected $jwt;
public function __construct(JWTAuth $jwt)
{
$this->jwt = $jwt;
}
public function Signin(Request $request)
{
$this->validate($request, [
'email' => 'required|email|max:100',
'password' => 'required|min:6',
]);
if (!$token = $this->jwt->attempt($request->only('email', 'password'))) {
return response()->json(['The credentials provided are invalid.'], 500);
}
return response()->json(compact('token'));
}
public function LogoutUser(Request $request){
$this->jwt->invalidate($this->jwt->getToken());
return response()->json([
'message' => 'User logged off successfully!'
], 200);
}
}
routes:
$app->group(['prefix' => 'api'], function($app){
$app->post('/signup', [
'uses' => 'UserController#Signup'
]);
$app->group(['middleware' => 'auth:api'], function($app){
$app->post('/logout',[
'uses' => 'UserController#LogoutUser'
]);
});
});
config/auth.php:
'defaults' => [
'guard' => env('AUTH_GUARD', 'api'),
],
'guards' => [
'api' => [
'driver' => 'jwt',
'provider' => 'users'
],
],
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => \App\User::class,
],
],
'passwords' => [
//
],
Any help will be greatly appreciated.

I have now got it working and leave behind the steps so if anybody else faces the same issue. The fix was to use CACHE_DRIVER=file in the .env file. I am not exactly sure why or how this fixes it but some research led me to this and was a result of trial and error things.

i find something may cause this problem.
see Tymon\JWTAuth\Providers\Storage\Illuminate
public function add($key, $value, $minutes)
{
// If the laravel version is 5.8 or higher then convert minutes to seconds.
if ($this->laravelVersion !== null
&& is_int($minutes)
&& version_compare($this->laravelVersion, '5.8', '>=')
) {
$minutes = $minutes * 60;
}
$this->cache()->put($key, $value, $minutes);
}
my cache driver is redis and when i invalidate a token, this function will be invoded。
Obviously $this->laravelVersion is null in lumen.
I just solved my issue
copy Tymon\JWTAuth\Providers\Storage\Illuminate to other folder
in my case, i copy to app\Providers\Storage\Illuminate
modify the add function
public function add($key, $value, $minutes)
{
$seconds = $minutes * 60;
$this->cache()->put($key, $value, $seconds);
}
remember change the namespace.
then modify the config/jwt
#'storage' => Tymon\JWTAuth\Providers\Storage\Illuminate::class,
'storage' => App\Providers\Storage\Illuminate::class,
Hope it helps

Related

Unauthenticated in Laravel JWT authentication

I used jwt-auth.
It works well in login, but when I try to register it and go through the login request the error below occurred.
"message": "Unauthenticated."
Here is the source code:
config/auth.php
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'jwt',
'provider' => 'users',
'hash' => True,
],
routes/api.php:
Route::group([
'middleware' => 'api',
'namespace' => 'App\Http\Controllers',//must include the path
'prefix' => 'auth'
], function ($router) {
Route::post('login', 'AuthController#login');
Route::post('signup', 'AuthController#signup');
Route::post('logout', 'AuthController#logout');
Route::post('refresh', 'AuthController#refresh');
Route::post('me', 'AuthController#me');
});
AuthController.php
public function __construct()
{
$this->middleware('auth:api', ['except' => ['login,signup']]);
}
public function login()
{
$credentials = request(['email', 'password']);
if (! $token = auth()->attempt($credentials)) {
return response()->json(['error' => 'Unauthorized'], 401);
}
return $this->respondWithToken($token);
}
public function signup(Request $request){
$validation = $request->validate([
'email' =>'required',
'name' =>'required',
'password'=>'required|min:6|confirmed',
]);
$data = array();
$data['name'] = $request->name;
$data['email'] = $request->email;
$data['password'] = Hash::make($request->password);
DB::table('users')->insert($data);
return $this->login($request);
}
I provided DB and Hash classes. In postman, I put the route in the POST method and set headers Accept and content-type as application/json formate and in the body part, I set x-www-form and input all the keys and values with confirmation_password key. It not inserting into the database and showing the error message. I tried it by clearing the config cache and route cache. I also tried it with raw and json format.
i think you problem with ur constractor middleware u didn't make the except value right u have to make to different values for except like that
$this->middleware('auth:api', ['except' => ['login','signup']]);
Better use create or save Method to store the new user. After you store user in your database you can generate a usertoken and send him back. And the user is logged in. Every further request is then regulated via the token.
$token = // generate an new token with the JWT toker Helper method ;
return response()->json([
'status' => 'ok',
'token' => $token,
'user' => $user
], 201);

Laravel 5.6 PasswordBroker change token duration time dynamically according to a specific situation

What I'm trying to achieve is when we create a user, he receives a login mail with a link that is only valid for 6 hours or so. This is not enough, and in most cases, we have to manually set the password for the user.
The user should have 3 days for creating his first password.
However, when a user clicks on forgot the password, the 6-hour limit is enough (because that is something that he does consciously).
Here's what I have so far!
Our store function in the UsersController looks like this:
public function store(StoreUser $request)
{
...
\DB::transaction(function () use ($request, $data) {
$roles = $request->input('roles');
$isInternal = $request->input('is_internal');
$customers = $request->input('customers', []);
/** #var User $user */
$user = $this->userRepository->create($data);
$user->assignRole($roles);
if ($isInternal == false && !empty($customers)) {
$user->customers()->sync($customers);
}
$token = app(PasswordBroker::class)->createToken($user);
$user->notify(new AccountActivationNotification($token));
});
return $this->respond()->success([], "User successfully created.");
}
Our reset and forgot functions:
public function reset(Request $request)
{
$request->validate([
'token' => 'required',
'email' => 'required|email',
'password' => 'required|confirmed|min:6',
]);
$credentials = $request->only('email', 'password', 'password_confirmation', 'token');
// Here we will attempt to reset the user's password. If it is successful we
// will update the password on an actual user model and persist it to the
// database. Otherwise we will parse the error and return the response.
$response = $this->passwordBroker->reset(
$credentials,
function ($user, $password) {
$user->password = $password;
$user->status = StatusesService::STATUS_ACTIVE;
$user->email_verified_at = now();
$user->save();
event(new PasswordReset($user));
}
);
return $response == $this->passwordBroker::PASSWORD_RESET
? $this->respond()->success()
: $this->respond()->validationFailed(trans($response));
}
public function forgot(Request $request)
{
$request->validate([
'email' => 'required|email',
]);
// We will send the password reset link to this user. Once we have attempted
// to send the link, we will examine the response then see the message we
// need to show to the user. Finally, we'll send out a proper response.
$response = $this->passwordBroker->sendResetLink(
$request->only('email')
);
return $response == $this->passwordBroker::RESET_LINK_SENT
? $this->respond()->success([], "Your password has been reset, please check your inbox.")
: $this->respond()->validationFailed(trans($response));
}
We already set two different configuration in config/auth.php:
'passwords' => [
'users' => [
'provider' => 'users',
'table' => 'password_resets',
'expire' => 4320, //3 days
],
'users_fpassword' => [
'provider' => 'users',
'table' => 'password_resets',
'expire' => 1440, //6 hours
],
],
What can we do to dynamically change between the configurations that we have in the config/auth.php accordingly to the situation described at the beginning of the post?
I think what are you looking for is how to set config value dynamically in Laravel you can easily do this using Laravel helper function.
config(['auth.passwords.users.expire' => 120]);
So in config file make it default 6 hrs expiration time.
'passwords' => [
'users' => [
'provider' => 'users',
'table' => 'password_resets',
'expire' => 1440, //6 hrs
],
],
and in controller function just add one line
public function store(StoreUser $request)
{
...
\DB::transaction(function () use ($request, $data) {
$roles = $request->input('roles');
$isInternal = $request->input('is_internal');
$customers = $request->input('customers', []);
/** #var User $user */
$user = $this->userRepository->create($data);
$user->assignRole($roles);
if ($isInternal == false && !empty($customers)) {
$user->customers()->sync($customers);
}
config(['auth.passwords.users.expire' => 4320]);
$token = app(PasswordBroker::class)->createToken($user);
$user->notify(new AccountActivationNotification($token));
});
return $this->respond()->success([], "User successfully created.");
}
I think, better solution for you is:
Changing scheme of password_resets table by adding field like expire_at, which stores expiring time of token.
Creating your own TokenRepository(implements Illuminate\Auth\Passwords\TokenRepositoryInterface). Inside it implement all logic: on token creating populate expire_at field, on token checking - verify that it's expiring time later than now.
Extend PasswordBroker by adding your own method, which allow you pass new token lifetime to TokenRepository.
You can add another password reset configuration in config/auth.php like:
'passwords' => [
'users' => [
'provider' => 'users',
'table' => 'password_resets',
'expire' => 60,
],
'invites' => [
'provider' => 'users',
'table' => 'password_resets',
'expire' => 1440,
],
],
This can later be used in your controller like:
if (!app('auth.password')->broker('invites')->tokenExists($user, $request->input('token'))) {
return redirect()->back()->withInput();
}
Well, adjust it to your needs. I tested this on laravel 6.x and it works correctly.

laravel and jwt: always return Unauthorized

I want to use JWT to login in my API but it always give me
error: "Unauthorized".
Before this, i already register the email and password of the user in my database before trying to login
here's my code :
class UserController extends Controller
{
public function __construct()
{
$this->middleware('auth:api', ['except' => 'login']);
}
public function login(Request $request) {
$validator = Validator::make($request->all(), [
'email' => 'required|email|max:255',
'password' => 'required|string|min:6|max:255',
]);
if($validator->fails()) {
return response()->json([
'status' => 'error',
'messages' => $validator->messages()
], 200);
}
if (! $token = auth()->attempt(['email' => $request->email, 'password' => $request->password])) {
return response()->json(['error' => 'Unauthorized'], 401);
}
return $this->respondWithToken($token);
}
protected function respondWithToken($token) {
return response()->json([
'access_token' => $token,
'token_type' => 'bearer',
'expires_in' => Auth::guard()->factory()->getTTL() * 60
]);
}
/**
* Get the guard to be used during authentication.
*
* #return \Illuminate\Contracts\Auth\Guard
*/
public function guard()
{
return Auth::guard('api');
}
here's my guard and default in config/auth.php :
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'jwt',
'provider' => 'users',
],
],
'defaults' => [
'guard' => 'api',
'passwords' => 'users',
],
here's my frontend using VueJs that does the login :
axios.post('http://localhost:8000/api/login', loginData)
.then(response => {
console.log(response)
})
.catch(error => {
commit('loginStop', error.response.data.error);
commit('updateAccessToken', null);
console.log(error.response);
})
here's my web.php :
Route::group(['prefix' => 'api'], function() {
Route::post('/register', 'UserController#register');
Route::post('/login', 'UserController#login');
});
Here's my output of php artisan route:list :
Based on the comments, you aren't hashing the password of the user that you inserted in the database.
But you have to do it as Auth::attempt checks if the provided password would match the hash stored in the database.
If you still want to register the user manually, you can hook up a tinker interactive shell with php artisan tinker command and then user Hash::make('yourpassword') to generate an hashed password using the setted Laravel's password hashing system (defaults to bcrypt).
Then you just have to copy the output string into your database. The login should finally work as the Auth guard now can check the user input agains a correct database user with a proper hashed password.
I had a totally different issue.
The auth()->attempt method was not comparing the password to my User model password for some reason.
After adding the following to my User model:
public function getAuthPassword() {
return $this->password;
}
Everything worked like a charm
The same problem to me, I solved by:
Use tinker to generate user credentials, email and password
content type in Headers: application/x-www-form-urlencoded

How to use token authentication in laravel web page

I am trying to use JWT for laravel web page instead of session. so I made some changes.
Installed jwt-auth and configure
Then changed default guard as api in config/auth.php
'defaults' => [
'guard' => 'api',
'passwords' => 'users',
],
'guards' => [
...
'api' => [
'driver' => 'token',
'provider' => 'users',
],
],
Now I am getting error
(1/1) FatalErrorException Call to undefined method
Illuminate\Auth\TokenGuard::attempt() in AuthenticatesUsers.php (line
75)
How to fix this and start token authentication for laravel web page(blades not API).
I'm also using jwt protecting our api. You should change your config like below:
'defaults' => [
'guard' => 'api',
'passwords' => 'users',
],
'guards' => [
...
'api' => [
'driver' => 'jwt', // KEY POINT!!!
'provider' => 'users',
],
],
Make sure the jwt library installed correctly:
Tymon\JWTAuth\Providers\LaravelServiceProvider::class is added in your config/app.php.
Your user model implements JWTSubject interface if you use eloquent model in your provider.
I found the solution here : https://github.com/tymondesigns/jwt-auth/issues/860
In /routes/api.php - added a few basic authentication routes
Route::post('login', 'Auth\LoginController#login');
Route::get('/user', function (Request $request) {
$user = $request->user();
return dd($user);
})->middleware('auth:api');
In /app/http/Controller/auth/LoginController.php
and then override methods in login contoller
public function login(Request $request)
{
$credentials = $request->only(["email","password"]);
if ($token = $this->guard()->attempt($credentials)) {
return $this->sendLoginResponse($request, $token);
}
$this->incrementLoginAttempts($request);
return $this->sendFailedLoginResponse($request);
}
protected function sendLoginResponse(Request $request, $token)
{
$this->clearLoginAttempts($request);
return $this->authenticated($request, $this->guard()->user(), $token);
}
protected function authenticated(Request $request, $user, $token)
{
setcookie("jwt_token", $token);
return redirect('/');
return response()->json([
'token' => $token,
]);
}
protected function sendFailedLoginResponse(Request $request)
{
return response()->json([
'message' => "not found",
], 401);
}
Adding middleware AddToken
public function handle($request, Closure $next)
{
$token = isset($_COOKIE["jwt_token"])?$_COOKIE["jwt_token"]:"";
//$request['token'] = $token;//this is working
$request->headers->set("Authorization", "Bearer $token");//this is working
$response = $next($request);
//$response->header('header name', 'header value');
return $response;
}
Register middleware in Kernel.php
protected $middleware = [
....
\App\Http\Middleware\AddToken::class,
];
I think you can try this :
'defaults' => [
'guard' => 'web',
'passwords' => 'users',
],
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
],
EDIT
You can find some help from the step by step example. In this example you need to focus on how to configure and use that token base authentication.
Hope this help you well.
Please refer this link. If you are using api as default then laravel authentication will throw an error.
Laravel uses default Session based authentication out of the box with the default scaffolding users-view-controller that you already have. You have additional means of adding your own custom guard in the doc, so you can make use of the guard as needed.
Therefore as #KevinPatel suggested, revert back to the default configuration, then in your route: group the route you want to be under JWT authentication, add the JWTMiddleware, in this case you have to update the controller responsible for your authentication to use the JWTAuth instead of the default auth.
You should check this answer if you need to understand it better check this answer on Laracasts
One recommended way to incorporate the JWTAuth is to go for Dingo API (of course you are not building api, but) because Dingo already added some flesh to the authentication and other routes management - so things are pretty easy to use and configure

How to generate api_token?

I've created code and authenticated user using guard but it doesn't provides me a api_token
/**
* This'll provide login authentication to the user
* #param Request $request
* #return json
*/
public function authenticate(Request $request)
{
//getting and setting locale for the request
$locale = ($request->header('lang-code') == 'KO') ? "ko" : "en";
app()->setLocale($locale);
$credentials = $request->only('email','password');
try {
// verify the credentials and create a token for the user
if (!$token = auth()->attempt($credentials)) {
return response()->json(['success' => parent::FAILURE, 'message' => trans('messages.api.login.failure')],401);
}
} catch (GeneralException $e) {
// something went wrong
return response()->json(['success' => parent::FAILURE, 'message' => trans('messages.api.login.tokenNC')],500);
}
return response()->json(['success' => parent::SUCCESS, 'message' => trans('messages.api.login.success'), 'data' => auth()->user()],200);
}
Above function is working fine but I'm not getting token when I use auth()->guard('api')->user()->api_token. This column is already within my DB even though I'm not able to generate api_token what can be the issue over here.
EDITED
routes/api.php:
Route::group(['namespace' => "Api\\v1", 'as' => 'api.v1.', 'prefix' => 'v1'], function () {
Route::any('/login', 'AccessController#authenticate');
});
config/auth.php:
'defaults' => [
'guard' => 'web',
'passwords' => 'users',
],
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
],
],
You may need to make sure that any routes that will be using Token Authentication are being protected by the auth:api middleware.
Like this example :
Route::group(['prefix' => 'api/v1', 'middleware' => 'auth:api'], function () {
Route::post('/authentificate', 'AuthController#authentificate');
});
You can use model mutators to do that automatically or override the boot method of your Model Class which is in your case User Model
protected static function boot()
{
parent::boot();
static::creating(function($model)
{
$model->api_token = $model->generateCode();
});
}
protected function generateCode()
{
return bin2hex(openssl_random_pseudo_bytes(16));
//you can use your own random fucntion here or you can use inbuilt Crypt funciton
}

Categories