JWT integration for Yii 2

Since author of sizeg/ yii2-jwt package is not maintaining it anymore I’ve decided to make a fork of it and keep it clean and up-to-date.

What’s changed in the fork?

  • JwtHttpBearerAuth extends yii\filters\auth\HttpBearerAuth now and properly handles authenticating failures.
  • Jwt component handles all signers provided by lcobucci/jwt package (including RSA and ECDSA).
  • Public key can be provided either as file path or Yii alias.
  • Validation claims can be provided to be tested against.
  • More tests for Travis.
  • Minimum Yii version raised to 2.0.14.
  • Minimum PHP version raised to 7.1.
7 Likes

Nice! Is it registered at https://www.yiiframework.com/extensions?

Yes It Is! :slight_smile:

3 Likes

Would help with this please, the JWT is generated, but I’m using it I get 401 error message. I tried to trace it with xdebug, the findIdentityByAccessToken is been called. The test is done with PostMan.

user model:

		public static function generateJwt ($email, $user_id, $registrationStamp) {

		$jti = $registrationStamp * 4 / 3 + $user_id;
		$uid = $user_id;
		$key = \Yii::$app->jwt->key . $user_id;
		$signer = new Sha512();

		//todo: check the expiry time/--------------------------------------/
		$token = \Yii::$app->jwt->getBuilder()
			->setIssuer(\Yii::$app->params[ 'hostInfo' ])// Configures the issuer (iss claim)
			->setAudience(\Yii::$app->params[ 'hostInfo' ])// Configures the audience (aud claim)
			->setId($jti, true)// Configures the id (jti claim), replicating as a header item
			->setIssuedAt(time())// Configures the time that the token was issue (iat claim)
			->setExpiration(time() + 3600 * 7)// Configures the expiration time of the token (exp claim)
			->set('email', $email)
			->set('uid', $uid)// Configures a new claim, called "uid"
			->sign($signer, $key)// creates a signature using "testing" as key
			->getToken();
		return $token;
	} 
		public static function findIdentityByAccessToken ($token, $type = null) {
		//throw new NotSupportedException('"findIdentityByAccessToken" is not implemented.');
		foreach ( self::$users as $user ) {
			if ( $user[ 'id' ] === (string) $token->getClaim('uid') ) {
				return new static($user);
			}
		}
		return false;
	}
		public function login ($email, $password) {
		if ( $this->validatePassword($password) AND $this->isUserActive() ) {
			//\Yii::$app->user->login(self::findByEmail($this->email));
			$token = (string) Token::generateJwt($this->email, $this->id, $this->created_at);
			return $token;
		}
		return false;
	}

Api/UserController

		public function behaviors () {
		$behaviors = parent::behaviors();

		$behaviors[ 'authenticator' ] = [
			'class' => \bizley\jwt\JwtHttpBearerAuth::class,
			'only'  => ['test'],
		];
		return $behaviors;
	}
public function actionTest () {
		return ['Test action'];
	}

I’m not sure what is this login() method of yours doing. Is it proper? Could you provide full User model?

The login() method is to validate username & password.
The user model is here https://pastebin.com/a3BXk1CJ (too long to post it here)

I can not find anywhere definition of this static $users variable called in line 77. Is it missing or am I blind?

You right, but method findIdentityByAccessToken in called, I checked this by xdebug.
But how to pass the $user value here, should I make new query? and why there is foreach loop?

Thanks

Oh, I understand now where did you get this code from - it’s the example from sizeg/yii2-jwt original package (I’m not providing such examples). As you can read there sizeg mentions using basic Yii 2 template in that example. The variable comes from here.

I see, my bad, $users is for the none DB users. My code I got from https://github.com/sizeg/yii2-jwt#installation
The users are in the DB, so how to do it?

Thanks,

Well, since all depends on your implementation I can only give you some clues how to do it. Take a look at the tests for extension, this might give you general idea.

Based on that I changed my code, the login method moved to api/UserController

		public function actionLogin () {
		$email = \Yii::$app->request->post('email');
		$password = \Yii::$app->request->post('password');

		if ( !$email or !$password )
			return ['result' => 'error', 'message' => 'Email or password error!'];

		//find the user
		$user = User::findByEmail($email); 
		if ( $user ) {
			//if password valid & account is active
			if ( $user->validatePassword($password) AND $user->isUserActive() ) {
				$token = Token::generateJwt($user->id, $user->created_at);
				User::$token = (string) $token;
				\Yii::$app->request->headers->set('Authorization', "Bearer ".(string)$token);
				return ['result' => 'success', 'message' => (string) $token];
			}
		}
		return ['result' => 'error', 'message' => 'Email or password error!'];
	}

and in the common/User model I changed this:

		public static function findIdentityByAccessToken ($token, $type = null) {
		if ( static::$token !== $token ) {
			return null;
		}
		return new static();
	}

and the method to generate the token:

		public static function generateJwt ($user_id, $registrationStamp) {

		$jti = "1234";//$registrationStamp * 4 / 2 + $user_id;
		$uid = $user_id;
		$key = \Yii::$app->jwt->key;
		$signer = new Sha512();

		//todo: check the expiry time/--------------------------------------/
		$token = \Yii::$app->jwt->getBuilder()
			->setIssuer(\Yii::$app->params[ 'hostInfo' ])// Configures the issuer (iss claim)
			->setAudience(\Yii::$app->params[ 'hostInfo' ])// Configures the audience (aud claim)
			->setId($jti, true)// Configures the id (jti claim), replicating as a header item
			->setIssuedAt(time())// Configures the time that the token was issue (iat claim)
			->setExpiration(time() + 3600 * 7)// Configures the expiration time of the token (exp claim)
			->setSubject($uid)// Configures a new claim, called "uid"
			->sign($signer, $key)// creates a signature using "testing" as key
			->getToken();
		return $token;
	}

But same thing, error 401, maybe I need custom auth?!
I’m using api module to serve the frontend (vue) and the backend.

You are assigning $token to User::$token which is lost immediately in next call (when you try to authenticate). Do you remember that PHP is stateless?

I would like to recommend you to analyze the examples you’ve been given so far to understand how it all works.

I know the the concept, but I don’t know to make with Yii. I tried many things that I wrote any to make works

v3.0.0 released!

This version supports lcobucci/jwt library in version 4 which has been significantly improved (and changed - be aware).

Please read the doc before using it (several examples have been provided there). Let me know if you have any suggestions or would like to report an issue.

2 Likes

v3.1.0 released adding support for EdDSA signers (with lcobucci/jwt 4.1.0).

2 Likes

v3.2.0 released:

  • Using local_file_reference (STORE_LOCAL_FILE_REFERENCE) as a key store option is now deprecated and will be removed in 4.0.0.
  • Attempt to use empty string as a key configuration is now throwing exception.
  • JwtHttpBearerAuth is not silencing JWT exceptions anymore allowing more developer friendly experience.
2 Likes

v3.3.0 released:

Added throwException option to JwtHttpBearerAuth.

Whether the filter should throw an exception i.e. if the token has an invalid format. If there are multiple auth filters (CompositeAuth) it can make sense to “silent fail” and pass the validation process to the next filter on the composite auth list. Default is true.

2 Likes

v3.4.0 released:

  • Added support for lcobucci/jwt 4.2.0 library
  • Added BLAKE2B signer
  • Added note for using unsafe signers
1 Like

v4.0.0 released:

This release adds compatibility with lcobucci/jwt v5 and introduces the following BC breaks:

  • Minimum required PHP version is 8.1
  • Empty signer, key, and signature are not allowed anymore
  • lcobucci/clock package is not provided by default anymore
  • store configuration key has been removed since it can hold only one value anyway