tymondesigns / jwt-auth

🔐 JSON Web Token Authentication for Laravel & Lumen
https://jwt-auth.com
MIT License
11.3k stars 1.54k forks source link

Issue token and refresh token : Laravel /jwt #1755

Open ahmedguesmi opened 5 years ago

ahmedguesmi commented 5 years ago

Your environment

Q A
Bug? no
New Feature? no / yes
Framework Laravel
Framework version 5.6
Package version 1.x.y
PHP version 7.x.y

I am in the course of developing an API, I have a refreshing token problem, at this moment I do a controller that receives the refreshing request with the valid token and the controller will create a new token and puts the last one to the blacklist, the Problem here is the mobile developer how will know the expiration time of the token I see that it is not practical to send at any time to test the validity of the token because the controller I use it can not refreshire an expired token for example: the validity of my token 60 min if the mobile developer does not make a request before this 60 min it will disconnect from the application because the token will expire and it cannot refresh, but if he sends a requests to refresh token before this 60 min everything will go well. my question is can I generate two tokens the first normal token and the second when the developer sends a requette and he receives a 401 request he uses the refresh token to generate another two tokens and so he stays connected until he makes a manual disconnection

my controller AUthcontroller :

 <?php
    namespace App\Http\Controllers;
    use Illuminate\Http\Request;
    use App\User;
    use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
    use Tymon\JWTAuth\Contracts\Providers\Auth;
    use Tymon\JWTAuth\Exceptions\JWTException;
    use Tymon\JWTAuth\Exceptions\TokenInvalidException;
    use Tymon\JWTAuth\Facades\JWTAuth;
    use Validator, Hash;
    use Illuminate\Support\Facades\Password;
    use Carbon\Carbon;
    use Config;
    use Artisan;
    class AuthController extends Controller
    {
        /**
         * API Register
         *
         * @param Request $request
         * @return \Illuminate\Http\JsonResponse
         */
        public function register( Request $request){
            $validator = Validator::make($request -> all(),[
                'email' => 'required|string|email|max:255|unique:users',
                'username' =>'required',
                'tel' => 'required',
                'name' => 'required',
                'lastname' => 'required',
                'adress' => 'required',
                'password'=> 'required'
            ]);

            if ($validator -> fails()) {
                # code...
                return response()->json($validator->errors());

            }

            $user_data = User::create([
                'name' => $request->get('name'),
                'email' => $request->get('email'),
                'tel' => $request->get('tel'),
                'username' => $request->get('username'),
                'lastname' => $request->get('lastname'),
                'adress' => $request->get('adress'),
                'password'=> bcrypt($request->get('password'))
            ]);
            $user = User::first();
            $token = JWTAuth::fromUser($user);

            return response()->json(['success'=> true,'token'=>compact('token'),'Data'=>$user_data, 'message'=> 'Thanks for signing up']);

        }

        /**
         * API Login, on success return JWT Auth token
         *
         * @param Request $request
         * @return \Illuminate\Http\JsonResponse
         */
        public function login(Request $request)
        {
            $credentials = $request->only('email', 'password');
            $rules = [
                'email' => 'required|email',
                'password' => 'required',
            ];
            $validator = Validator::make($credentials, $rules);
            if($validator->fails()) {
                return response()->json(['success'=> false, 'error'=> $validator->messages()], 401);
            }
            try {
                // attempt to verify the credentials and create a token for the user
                $time = isset($request->time) ? $request->time : Config::get('jwt.ttl');
                // print_r(Carbon::now()->addSeconds($time)->timestamp);
                Config::set('jwt.ttl', $time);
                Artisan::call('config:clear');
                if (! $token = JWTAuth::attempt($credentials)) {
                    return response()->json(['success' => false, 'error' => 'We cant find an account with this credentials. Please make sure you entered the right information and you have verified your email address.'], 404);
                }
            } catch (JWTException $e) {
                // something went wrong whilst attempting to encode the token
                return response()->json(['success' => false, 'error' => 'Failed to login, please try again.'], 500);
            }
            // all good so return the token
           // $refresh_token = auth('api')->refresh($token)
         //  $token = Auth::refresh($token);
          //  $token = JWTAuth::refresh($token);

            return response()->json(['success' => true, 'data'=> [ 'token' => $token ]], 200);
        }
        /**
         * Log out
         * Invalidate the token, so user cannot use it anymore
         * They have to relogin to get a new token
         *
         * @param Request $request
         */
        public function logout(Request $request) {
            try {
                JWTAuth::invalidate($request->header('Authorization'));
                return response()->json(['success' => true, 'message'=> "You have successfully logged out."]);
            } catch (JWTException $e) {
                // something went wrong whilst attempting to encode the token
                return response()->json(['success' => false, 'error' => 'Failed to logout, please try again.'], 500);
            }
        }
        public function token($token = null){
            $token = $token ? $token : JWTAuth::getToken();
            if(!$token){
                throw new BadRequestHtttpException('Token not provided');
            }
            try{
                $token = JWTAuth::refresh($token);
            }catch(TokenInvalidException $e){
                throw new AccessDeniedHttpException('The token is invalid');
            }
            return response()->json(['success' => true, 'data' => $token], 200);
        }
        public function recover(Request $request)
        {
            $user = User::where('email', $request->email)->first();
            if (!$user) {
                $error_message = "Your email address was not found.";
                return response()->json(['success' => false, 'error' => ['email'=> $error_message]], 401);
            }
            try {
                Password::sendResetLink($request->only('email'), function (Message $message) {
                    $message->subject('Your Password Reset Link');
                });
            } catch (\Exception $e) {
                //Return with error
                $error_message = $e->getMessage();
                return response()->json(['success' => false, 'error' => $error_message], 401);
            }
            return response()->json([
                'success' => true, 'data'=> ['message'=> 'A reset email has been sent! Please check your email.']
            ]);
        }
        public function changePassword(Request $request)
        {
            $inputs = $request->only('oldpassword','newpassword');
            $rules = [
                'oldpassword' => 'required',
                'newpassword' => 'required',
            ];
            $validator = Validator::make($inputs, $rules);
            if($validator->fails()) {
                return response()->json(['success'=> false, 'error'=> $validator->messages()], 401);
            }
            try {
                $authuser = auth()->user();
                if (! Hash::check($request->oldpassword, $authuser->password) ){
                    return response()->json(['success' => false, 'error' => 'You enter wrong old password'], 404);
                }
                $updated = User::where('email', $authuser->email)->update(['password' =>  Hash::make($request->newpassword)]);
                if($updated){
                    $gettoken = $this->token($request->header('Authorization'));
                    $token = $gettoken->original['data'];
                }
            } catch (JWTException $e) {
                // something went wrong whilst attempting to encode the token
                return response()->json(['success' => false, 'error' => 'Failed to change password, please try again.'], 500);
            }
            return response()->json(['success' => true, 'data'=> [ 'message' =>'Password successfully changed',"token"=>$token]], 200);
        }
    }
ahmedguesmi commented 5 years ago

@tymondesigns

lorisleiva commented 5 years ago

What you're describing is implementing the access/refresh token pair from OAuth2. Here is the difference between an access token and refresh token from stackoverflow:

The idea of refresh tokens is that if an access token is compromised, because it is short-lived, the attacker has a limited window in which to abuse it.

Refresh tokens, if compromised, are useless because the attacker requires the client id and secret in addition to the refresh token in order to gain an access token.

However, JavaScript and mobile applications live in userland and, therefore, are public clients. Public clients are clients that cannot safely store a client_secret to ensure their identity.

As a result, public clients cannot leverage the extra security of having refresh tokens. Refresh tokens that do not need client identification to generate new access tokens are as powerful as access tokens.

Therefore your suggestion is equivalent to extending the time window of access tokens.

Btw, lots of applications do that. E.g. Koel from Phanan.

bert-w commented 5 years ago

@lorisleiva that made some sense to me thanks. So this basically means the JWT_REFRESH_TTL in config/jwt.php is of no use in these situations? I dont see how this variable is of any use when refresh_tokens are not in use.

1863

stale[bot] commented 3 years ago

Is this still relevant? If so, what is blocking it? Is there anything you can do to help move it forward?

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.