Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can't get token with Laravel 6.0 and jwt-auth 1.0.0-rc.5 when trying to authenticate #1880

Open
osymo opened this issue Sep 29, 2019 · 30 comments
Labels

Comments

@osymo
Copy link

osymo commented Sep 29, 2019

Token value is always false

Hello. I used jwt-auth 1.0.0-rc.5 to handle authentication with laravel 6.0.
My AuthController code is pasted below.

When registering a new user with the method register in AuthController, I succesfully get a jwt token.

But when trying to athenticate the user I created earlier, token value is always false

`<?php

namespace App\Http\Controllers;

use DB;
use Hash;
use App\User;
use App\RoleUser;
use Illuminate\Http\Request;

class AuthController extends Controller
{
public function authenticate(Request $request)
{
$credentials = $request->only('email', 'password');

	$token = auth()->attempt($credentials);

    // var_dump(Hash::make($credentials['password']));
    // var_dump($token);
    
	if($token)
		return $token;
	return response()->json(['error' => 'Unauthorized'], 401);
}

public function register(Request $request)
{

    $request->validate([
        'role'      =>  'required',
        'email'     =>  'required|unique:users',
        'password'  =>  'required',
        'firstname' =>  'required',
        'lastname'  =>  'required'
    ],[
        'role'          => 'Le niveau de role est requis',
        'email.unique'  => "L'adresse email est requise",
        'password'      => 'Le mot de passe est requis',
        'firstname'     => 'Le prénom est requis',
        'lastname'      => 'Le nom de famille est requis'
    ]);

    DB::beginTransaction();

        $user = User::create([
         'email'        => $request->email,
         'password'     => $request->password,
         'firstname'    => $request->firstname,
         'lastname'     => $request->lastname
        ]);

        RoleUser::create([
            'role_name' => $request->role,
            'user_id'  => $user->id
        ]);

    DB::commit();

    $token = auth()->login($user);

    // var_dump($token);

    return $this->respondWithToken($token);
}

 protected function respondWithToken($token)
{
    return response()->json([
        'access_token' => $token,
        'token_type'   => 'bearer',
        'expires_in'   => auth()->factory()->getTTL() * 60
    ]);
}

}
`

`<?php

return [

/*
|--------------------------------------------------------------------------
| Authentication Defaults
|--------------------------------------------------------------------------
|
| This option controls the default authentication "guard" and password
| reset options for your application. You may change these defaults
| as required, but they're a perfect start for most applications.
|
*/

'defaults' => [
    'guard' => 'api',
    'passwords' => 'users',
],

/*
|--------------------------------------------------------------------------
| Authentication Guards
|--------------------------------------------------------------------------
|
| Next, you may define every authentication guard for your application.
| Of course, a great default configuration has been defined for you
| here which uses session storage and the Eloquent user provider.
|
| All authentication drivers have a user provider. This defines how the
| users are actually retrieved out of your database or other storage
| mechanisms used by this application to persist your user's data.
|
| Supported: "session", "token"
|
*/

'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'users',
    ],

    'api' => [
        'driver' => 'jwt',
        'provider' => 'users',
        'hash' => false,
    ],
],

/*
|--------------------------------------------------------------------------
| User Providers
|--------------------------------------------------------------------------
|
| All authentication drivers have a user provider. This defines how the
| users are actually retrieved out of your database or other storage
| mechanisms used by this application to persist your user's data.
|
| If you have multiple user tables or models you may configure multiple
| sources which represent each model / table. These sources may then
| be assigned to any extra authentication guards you have defined.
|
| Supported: "database", "eloquent"
|
*/

'providers' => [
    'users' => [
        'driver' => 'eloquent',
        'model' => App\User::class,
    ],

    // 'users' => [
    //     'driver' => 'database',
    //     'table' => 'users',
    // ],
],

/*
|--------------------------------------------------------------------------
| Resetting Passwords
|--------------------------------------------------------------------------
|
| You may specify multiple password reset configurations if you have more
| than one user table or model in the application and you want to have
| separate password reset settings based on the specific user types.
|
| The expire time is the number of minutes that the reset token should be
| considered valid. This security feature keeps tokens short-lived so
| they have less time to be guessed. You may change this as needed.
|
*/

'passwords' => [
    'users' => [
        'provider' => 'users',
        'table' => 'password_resets',
        'expire' => 60,
    ],
],

];
`

@ianmustafa
Copy link

Looks like I don't find any sign of password hashing in AuthController@register. Maybe that's the case?

I'm able to authenticate the user just fine, using the quick start guide

@osymo
Copy link
Author

osymo commented Oct 2, 2019

Hello ianMustafa.

Thank you for your answer.

Password is hashed in the User class by defining a setter.

Please see code below.

`<?php

namespace App;

use Hash;
use Illuminate\Notifications\Notifiable;
use Tymon\JWTAuth\Contracts\JWTSubject;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Database\Eloquent\SoftDeletes;

class User extends Authenticatable implements JWTSubject
{
use Notifiable;
use SoftDeletes;

/**
 * The attributes that are mass assignable.
 *
 * @var array
 */
protected $fillable = [
    'firstname', 'lastname', 'email','password'
];

/**
 * The attributes that should be hidden for arrays.
 *
 * @var array
 */
protected $hidden = [
    'password', 'remember_token',
];

/**
 * The attributes that should be cast to native types.
 *
 * @var array
 */
protected $casts = [
    'email_verified_at' => 'datetime',
];

public function getJWTIdentifier()
{
    return $this->getKey();
}

public function getJWTCustomClaims()
{
    return [];
}
public function setPasswordAttribute($password)
{
    if ( !empty($password) ) {
        $this->attributes['password'] = Hash::make($password);
    }
}

public function roles()
{
    return $this->belongsToMany('App\Role');
} 

}
`

@tvpeter
Copy link

tvpeter commented Oct 22, 2019

Having the same issue with Laravel 6.3.0.

@specialtactics
Copy link

specialtactics commented Oct 22, 2019

I just upgraded one of our projects to Laravel 6.3, we have millions of tests and haven't had any auth issues at all, so I find it highly unlikely there's a specific problem with this package.

I don't see any problems in your code, and I even tried it locally (we do our auth a bit different), and it works fine.

Really not sure what the underlying issue for you is, but it's gonna be something weird ! If your project is public, feel free to link to the repo and we can have a closer look.

Only other question I was gonna ask I guess is how closely you debugged this. Why are you returning just the token and not a json response containing the token as well? Looks a bit weird.

@tvpeter
Copy link

tvpeter commented Oct 23, 2019

@specialtactics Thank you for offering to help fix the issue. I haven't written tests for the project yet, but here's the repo link.

Why are you returning just the token and not a json response containing the token as well? Looks a bit weird.

I am returning the token in a json response along with the bearer and expiration time.

@tvpeter
Copy link

tvpeter commented Oct 24, 2019

@specialtactics Thank you for offering to help fix the issue. I haven't written tests for the project yet, but here's the repo link.

Why are you returning just the token and not a json response containing the token as well? Looks a bit weird.

I am returning the token in a json response along with the bearer and expiration time.

I have finally understood why it was failing. After registration, the emails were not verified and it requires emails to be verified before login.

@osymo
Copy link
Author

osymo commented Oct 24, 2019

Hello Guys.
@specialtactics like @tvpeter said, in the code posted above I'm returning the token in a json response using respondWithToken() method.

@tvpeter I'm at work now but will try your solution later at home.

Thanks to all.

@ratwizzard
Copy link

I bet you have done what I did and double hashed your password. If you follow the documentation and use the default laravel registration function (which includes password hashing) you will end up double hashing your password by putting this into your User model:

    public function setPasswordAttribute($password)
    {
        if ( !empty($password) ) {
            $this->attributes['password'] = Hash::make($password);
        }
    }

Make sure that you are not hashing your password in your registration controller!

@I2C-RoyYou
Copy link

I2C-RoyYou commented Nov 15, 2019

Hi, I'm using "tymon/jwt-auth": "^1.0.0-rc.5" ,but still get 401 Unauthorized , I can get the token , but use it to Bearer will get the 401 , is there anyone can help?

@ratwizzard
Copy link

@I2C-RoyYou Can you show your controller functions for registration, your config file and your User model?

@I2C-RoyYou
Copy link

I2C-RoyYou commented Nov 18, 2019

@I2C-RoyYou Can you show your controller functions for registration, your config file and your User model?

Hi @brobey8 ! here's my code:

config/auth.php:

<?php

return [

    /*
    |--------------------------------------------------------------------------
    | Authentication Defaults
    |--------------------------------------------------------------------------
    |
    | This option controls the default authentication "guard" and password
    | reset options for your application. You may change these defaults
    | as required, but they're a perfect start for most applications.
    |
    */

    'defaults' => [
        'guard' => 'web',
        // 'guard' => 'api',
        'passwords' => 'users',
    ],

    /*
    |--------------------------------------------------------------------------
    | Authentication Guards
    |--------------------------------------------------------------------------
    |
    | Next, you may define every authentication guard for your application.
    | Of course, a great default configuration has been defined for you
    | here which uses session storage and the Eloquent user provider.
    |
    | All authentication drivers have a user provider. This defines how the
    | users are actually retrieved out of your database or other storage
    | mechanisms used by this application to persist your user's data.
    |
    | Supported: "session", "token"
    |
    */

    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],

        'api' => [
            // 'driver' => 'token',
            'driver' => 'jwt',
            'provider' => 'users',
            // 'hash' => false,
        ],
    ],

    /*
    |--------------------------------------------------------------------------
    | User Providers
    |--------------------------------------------------------------------------
    |
    | All authentication drivers have a user provider. This defines how the
    | users are actually retrieved out of your database or other storage
    | mechanisms used by this application to persist your user's data.
    |
    | If you have multiple user tables or models you may configure multiple
    | sources which represent each model / table. These sources may then
    | be assigned to any extra authentication guards you have defined.
    |
    | Supported: "database", "eloquent"
    |
    */

    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\User::class,
        ],

        // 'users' => [
        //     'driver' => 'database',
        //     'table' => 'users',
        // ],
    ],

    /*
    |--------------------------------------------------------------------------
    | Resetting Passwords
    |--------------------------------------------------------------------------
    |
    | You may specify multiple password reset configurations if you have more
    | than one user table or model in the application and you want to have
    | separate password reset settings based on the specific user types.
    |
    | The expire time is the number of minutes that the reset token should be
    | considered valid. This security feature keeps tokens short-lived so
    | they have less time to be guessed. You may change this as needed.
    |
    */

    'passwords' => [
        'users' => [
            'provider' => 'users',
            'table' => 'password_resets',
            'expire' => 60,
        ],
    ],

    /*
    |--------------------------------------------------------------------------
    | Password Confirmation Timeout
    |--------------------------------------------------------------------------
    |
    | Here you may define the amount of seconds before a password confirmation
    | times out and the user is prompted to re-enter their password via the
    | confirmation screen. By default, the timeout lasts for three hours.
    |
    */

    'password_timeout' => 10800,

];

AuthController:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class AuthController extends Controller
{
	
	/**
	* Create a new AuthController instance.
	*
	* @return void
	*/
	public function __construct()
	{
		//全部都要認證 除了login
		$this->middleware('jwt.auth:api', ['except' => ['login']]);
	}


	/**
	* Get a JWT via given credentials.
	*
	* @return \Illuminate\Http\JsonResponse
	*/
	public function login()
	{
		// $credentials = request(['email', 'password']);
		$credentials = request(['account', 'password']);



		// if (! $token = auth()->attempt($credentials)) {
		if (! $token = $this->guard()->attempt($credentials)) {
			return response()->json(['error' => 'Unauthorized'], 401);
		}

		return $this->respondWithToken($token);
	}


	/**
	* Get the authenticated User.
	*
	* @return \Illuminate\Http\JsonResponse
	*/
	public function me()
	{
		// return response()->json(auth()->user());
		return response()->json($this->guard()->user());
	}


	/**
	* Log the user out (Invalidate the token).
	*
	* @return \Illuminate\Http\JsonResponse
	*/
	public function logout()
	{
		// auth()->logout();
		$this->guard()->logout();

		return response()->json(['message' => 'Successfully logged out']);
	}


	/**
	* Refresh a token.
	*
	* @return \Illuminate\Http\JsonResponse
	*/
	public function refresh()
	{
		// return $this->respondWithToken(auth()->refresh());
		return $this->respondWithToken($this->guard()->refresh());
	}


	/**
	* Get the token array structure.
	*
	* @param  string $token
	*
	* @return \Illuminate\Http\JsonResponse
	*/
	protected function respondWithToken($token)
	{
		return response()->json([
			'access_token' => $token,
			'token_type' => 'bearer',
			'expires_in' => $this->guard()->factory()->getTTL() * 60
			// 'expires_in' => auth()->factory()->getTTL()
		]);
	}


	private function guard()
	{
		// return auth()->guard('api');
		return auth('api');
	}
}

routes/api.php:

<?php

use Illuminate\Http\Request;

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/

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::middleware('jwt.auth:api')->group(function () {
	Route::apiResource('category', 'API\\CategoryController');
});

Both my localhost and server can get token, but server call api/category will get 401 Unauthorized ...
don't know where the error is ...

P.s. my .env file has jwt_secret too!

@ratwizzard
Copy link

@I2C-RoyYou I noticed that you have your guards configured like this:


    'defaults' => [
        'guard' => 'web',
        // 'guard' => 'api',
        'passwords' => 'users',
    ],

But only your api guard is configured with jwt:

        'api' => [
            // 'driver' => 'token',
            'driver' => 'jwt',
            'provider' => 'users',
            // 'hash' => false,
        ],

Your default guards should look like this (from the docs):

'defaults' => [
    'guard' => 'api',
    'passwords' => 'users',
],

You may need to go through the docs again, specifically this page: https://jwt-auth.readthedocs.io/en/develop/quick-start/ and make sure your setup is inline with the docs.

@I2C-RoyYou
Copy link

@I2C-RoyYou I noticed that you have your guards configured like this:


    'defaults' => [
        'guard' => 'web',
        // 'guard' => 'api',
        'passwords' => 'users',
    ],

But only your api guard is configured with jwt:

        'api' => [
            // 'driver' => 'token',
            'driver' => 'jwt',
            'provider' => 'users',
            // 'hash' => false,
        ],

Your default guards should look like this (from the docs):

'defaults' => [
    'guard' => 'api',
    'passwords' => 'users',
],

You may need to go through the docs again, specifically this page: https://jwt-auth.readthedocs.io/en/develop/quick-start/ and make sure your setup is inline with the docs.

Hi @brobey8 but this website has web login page , user can login to maintain some data, and also provide api , that frontend can get article , so I that the default keep to web !

@ratwizzard
Copy link

@I2C-RoyYou Have you tried making the api/category requests when the default guard is set to api?

@I2C-RoyYou
Copy link

@I2C-RoyYou Have you tried making the api/category requests when the default guard is set to api?
@brobey8
QQ still same error...

@ratwizzard
Copy link

@I2C-RoyYou Have you checked that you are not double hashing your password and made sure that your requests are formatted correctly? What does your user model and registration function look like?

@I2C-RoyYou
Copy link

@I2C-RoyYou Have you checked that you are not double hashing your password and made sure that your requests are formatted correctly? What does your user model and registration function look like?

Hi @brobey8 here is my User.php :


<?php

namespace App;

use Tymon\JWTAuth\Contracts\JWTSubject;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable implements JWTSubject
{
	use Notifiable;

	protected $table = 'user';

	/**
	 * The attributes that are mass assignable.
	 *
	 * @var array
	 */
	protected $fillable = [
		'name', 'email', 'account', 'password',
	];

	/**
	 * The attributes that should be hidden for arrays.
	 *
	 * @var array
	 */
	protected $hidden = [
		'password', 'remember_token',
	];

	/**
	 * The attributes that should be cast to native types.
	 *
	 * @var array
	 */
	protected $casts = [
		'email_verified_at' => 'datetime',
	];


 	/**
	* Get the identifier that will be stored in the subject claim of the JWT.
	*
	* @return mixed
	*/
	public function getJWTIdentifier()
	{
	  return $this->getKey();
	}

	/**
	* Return a key value array, containing any custom claims to be added to the JWT.
	*
	* @return array
	*/
	public function getJWTCustomClaims()
	{
	  return [];
	}
}


but if I have double hashing problem , why my localhost can work? and if password is wrong, then I can't get the token , right? now my error is I can get the token by login api , but when I add this token to header , my server return 401 Unauthorized ! so confused QQ

@ratwizzard
Copy link

@I2C-RoyYou does the header look like this?

Authorization: bearer <token>

@I2C-RoyYou
Copy link

@I2C-RoyYou does the header look like this?

Authorization: bearer <token>

yes~

@ratwizzard
Copy link

@I2C-RoyYou :( Can you commit your code somewhere so I can have a look?

@I2C-RoyYou
Copy link

@I2C-RoyYou :( Can you commit your code somewhere so I can have a look?

sorry , it's company's code , I can't .

@I2C-RoyYou
Copy link

I2C-RoyYou commented Nov 18, 2019

@brobey8 I got a temporary solution , I change token position, put it to url parameters. like doc says ,
use http://<your api url>?token=<your token> , and cancel bearar auth , then the auth pass , still don't know why, but let's do it first! thanks your help!!!

@manuel-84
Copy link

manuel-84 commented Nov 28, 2019

Same problem here. I can autenticate the first user I created but not new ones... and they are stored the same way with Hash::make
Then I moved the Hash::make function from the controllers to the model and recreated the users again... and it works.. without a good reason for me

@DartedMonki
Copy link

DartedMonki commented Feb 22, 2020

I got the same problem too, can't figure the problem out. I have tested the 1.0.0.rc5.1 and still doesn't work for me. What I do is I just followed this tutorial EXACTLY with new laravel project (6.13) but the problem still persists. Does the problem is because of my OS (Windows?), or because the XAMPP? Tutorial Link

@I2C-RoyYou
Copy link

I got the same problem too, can't figure the problem out. I have tested the 1.0.0.rc5.1 and still doesn't work for me. What I do is I just followed this tutorial EXACTLY with new laravel project (6.13) but the problem still persists. Does the problem is because of my OS (Windows?), or because the XAMPP? Tutorial Link

have you try this? I use this and success auth!

change token position, put it to url parameters. like doc says ,
use http://<your api url>?token=<your token> , and cancel bearar auth

@rodrigopa
Copy link

protected $hidden = [
'password', 'remember_token',
];

Remove 'password' from $hidden

@Scyllaly
Copy link

I got the same problem too, can't figure the problem out. I have tested the 1.0.0.rc5.1 and still doesn't work for me. What I do is I just followed this tutorial EXACTLY with new laravel project (6.13) but the problem still persists. Does the problem is because of my OS (Windows?), or because the XAMPP? Tutorial Link

have you try this? I use this and success auth!

change token position, put it to url parameters. like doc says ,
use http://<your api url>?token=<your token> , and cancel bearar auth

i have same problem ... i do not know why , i have been put the header key Authorization , value bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9zc3JwYW5lbC50ZXN0XC9hcGlcL2NsaWVudFwvdjFcL2xvZ2luIiwiaWF0IjoxNTk1OTQ3MzU0LCJleHAiOjE1OTU5NTA5NTQsIm5iZiI6MTU5NTk0NzM1NCwianRpIjoidE41NVpNUTZFUU9nZGVITyIsInN1YiI6NSwicHJ2IjoiMjNiZDVjODk0OWY2MDBhZGIzOWU3MDFjNDAwODcyZGI3YTU5NzZmNyJ9.fIPE4gKe__bXoQNhHP4NXsOvXtG5xp2yF_UeuB6Uo8g , and it can't be refresh token, it's always show me the error 'A token is required'

@Scyllaly
Copy link

Scyllaly commented Jul 28, 2020

@I2C-RoyYou Hi,i has been sloved this problem. You can read the wiki from https://github.com/tymondesigns/jwt-auth/wiki/Authentication

It tell us , when you login by the api middleware(jwt), and then, if you want to refresh the user's token,you need to parsed the user's token in request headers or in input request params firstly.

LOOK AT THIS:

// this will set the token on the object
JWTAuth::parseToken();

// and you can continue to chain methods
$user = JWTAuth::parseToken()->authenticate();

DO NOT READ THE https://jwt-auth.readthedocs.io/en/develop/auth-guard/, IT CAN NOT TELL US ABOUT DEFAULT GUARD WHICH IS NOT api

MY CODE, MAY BE HELPFUL TO U:


    public function login(Request $request)
    {
        $credentials = $request->only('username', 'password');

        try {
            if (!$token = JWTAuth::attempt($credentials)) {
                return response()->json(['error' => 'invalid_credentials'], 401);
            }

            return response()->json(['data' => $this->makeToken($token)]);
        } catch (JWTException $e) {
            return response()->json(['error' => Str::slug($e->getMessage(), '_')], 500);
        }
    }

    
    public function logout(Request $request)
    {
        auth('api')->logout();

        return response()->json(['message' => 'logout_success']);
    }

    
    public function refresh(Request $request)
    {
        try {
            return response()->json(['data' => $this->makeToken(JWTAuth::parseToken()->refresh())]);
        } catch (JWTException $e) {
            info($e);

            return response()->json(['error' => Str::slug($e->getMessage(), '_')], 401);
        }
    }

    
    public function me(Request $request)
    {
        try {
            return response()->json(['data' => JWTAuth::parseToken()->authenticate()]);
        } catch (JWTException $e) {
            info($e);

            return response()->json(['error' => Str::slug($e->getMessage(), '_')], 401);
        }
    }

protected function makeToken($token)
    {
        return [
            'access_token' => $token,
            'token_type'   => 'bearer',
            'expires_in'   => JWTAuth::factory()->getTTL() * 60
        ];
    }

IMPORTANT : JWTAuth::parseToken()->refresh()

now, you can put the header parma Authorization: bearer {user's token} for refresh user's token from your client

@stale
Copy link

stale bot commented Dec 25, 2020

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.

@stale stale bot added the stale label Dec 25, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

11 participants