Skip to content

Commit

Permalink
First commit
Browse files Browse the repository at this point in the history
  • Loading branch information
numelion committed Oct 19, 2022
1 parent ab5d7e0 commit 11eaeee
Show file tree
Hide file tree
Showing 12 changed files with 1,038 additions and 2 deletions.
6 changes: 6 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Ignore all test and documentation for archive
/.gitattributes export-ignore
/.gitignore export-ignore
/.travis.yml export-ignore
/phpunit.xml.dist export-ignore
/tests export-ignore
35 changes: 35 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# phpstorm project files
.idea

# netbeans project files
nbproject

# zend studio for eclipse project files
.buildpath
.project
.settings

# windows thumbnail cache
Thumbs.db

# composer vendor dir
/vendor

/composer.lock

# composer itself is not needed
composer.phar

# Mac DS_Store Files
.DS_Store

# phpunit itself is not needed
phpunit.phar
# local phpunit config
/phpunit.xml

# local tests configuration
/tests/data/config.local.php

# runtime cache
/tests/runtime
20 changes: 20 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
language: php

php:
- 5.6
- 7.0
- 7.1
- 7.2
- 8.0
- 8.1

# faster builds on new travis setup not using sudo
sudo: false

install:
# - composer global require "fxp/composer-asset-plugin:~1.4.4"
- export PATH="$HOME/.composer/vendor/bin:$PATH"
- composer install --prefer-dist --no-interaction

#script:
# - phpunit
177 changes: 177 additions & 0 deletions Jwt.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
<?php

namespace numelion\jwt;

use Lcobucci\JWT\Builder;
use Lcobucci\JWT\Claim\Factory as ClaimFactory;
use Lcobucci\JWT\Parser;
use Lcobucci\JWT\Parsing\Decoder;
use Lcobucci\JWT\Parsing\Encoder;
use Lcobucci\JWT\Signer;
use Lcobucci\JWT\Signer\Key;
use Lcobucci\JWT\Token;
use Lcobucci\JWT\ValidationData;
use Yii;
use yii\base\Component;
use yii\base\InvalidArgumentException;

/**
* JSON Web Token implementation, based on this library:
* https://github.com/lcobucci/jwt
*
* @author Dmitriy Demin <[email protected]>
* @since 1.0.0-a
*/
class Jwt extends Component
{

/**
* @var array Supported algorithms
*/
public $supportedAlgs = [
'HS256' => \Lcobucci\JWT\Signer\Hmac\Sha256::class,
'HS384' => \Lcobucci\JWT\Signer\Hmac\Sha384::class,
'HS512' => \Lcobucci\JWT\Signer\Hmac\Sha512::class,
'ES256' => \Lcobucci\JWT\Signer\Ecdsa\Sha256::class,
'ES384' => \Lcobucci\JWT\Signer\Ecdsa\Sha384::class,
'ES512' => \Lcobucci\JWT\Signer\Ecdsa\Sha512::class,
'RS256' => \Lcobucci\JWT\Signer\Rsa\Sha256::class,
'RS384' => \Lcobucci\JWT\Signer\Rsa\Sha384::class,
'RS512' => \Lcobucci\JWT\Signer\Rsa\Sha512::class,
];

/**
* @var Key|string $key The key
*/
public $key;

/**
* @var string|array|callable \numelion\jwtJwtValidationData
* @see [[Yii::createObject()]]
*/
public $jwtValidationData = JwtValidationData::class;

/**
* @see [[Lcobucci\JWT\Builder::__construct()]]
* @param Encoder|null $encoder
* @param ClaimFactory|null $claimFactory
* @return Builder
*/
public function getBuilder(Encoder $encoder = null, ClaimFactory $claimFactory = null)
{
return new Builder($encoder, $claimFactory);
}

/**
* @see [[Lcobucci\JWT\Parser::__construct()]]
* @param Decoder|null $decoder
* @param ClaimFactory|null $claimFactory
* @return Parser
*/
public function getParser(Decoder $decoder = null, ClaimFactory $claimFactory = null)
{
return new Parser($decoder, $claimFactory);
}

/**
* @see [[Lcobucci\JWT\ValidationData::__construct()]]
* @return ValidationData
*/
public function getValidationData()
{
return Yii::createObject($this->jwtValidationData)->getValidationData();
}

/**
* @param string $alg
* @return Signer
*/
public function getSigner($alg)
{
$class = $this->supportedAlgs[$alg];

return new $class();
}

/**
* @param strng $content
* @param string|null $passphrase
* @return Key
*/
public function getKey($content = null, $passphrase = null)
{
$content = $content ?: $this->key;

if ($content instanceof Key) {
return $content;
}

return new Key($content, $passphrase);
}

/**
* Parses the JWT and returns a token class
* @param string $token JWT
* @param bool $validate
* @param bool $verify
* @return Token|null
* @throws \Throwable
*/
public function loadToken($token, $validate = true, $verify = true)
{
try {
$token = $this->getParser()->parse((string) $token);
} catch (\RuntimeException $e) {
Yii::warning('Invalid JWT provided: ' . $e->getMessage(), 'jwt');
return null;
} catch (\InvalidArgumentException $e) {
Yii::warning('Invalid JWT provided: ' . $e->getMessage(), 'jwt');
return null;
}

if ($validate && !$this->validateToken($token)) {
return null;
}

if ($verify && !$this->verifyToken($token)) {
return null;
}

return $token;
}

/**
* Validate token
* @param Token $token token object
* @param int|null $currentTime
* @return bool
*/
public function validateToken(Token $token, $currentTime = null)
{
$validationData = $this->getValidationData();
if ($currentTime !== null) {
$validationData->setCurrentTime($currentTime);
}
return $token->validate($validationData);
}

/**
* Validate token
* @param Token $token token object
* @return bool
* @throws \Throwable
*/
public function verifyToken(Token $token)
{
$alg = $token->getHeader('alg');

if (empty($this->supportedAlgs[$alg])) {
throw new InvalidArgumentException('Algorithm not supported');
}

/** @var Signer $signer */
$signer = Yii::createObject($this->supportedAlgs[$alg]);

return $token->verify($signer, $this->key);
}
}
115 changes: 115 additions & 0 deletions JwtHttpBearerAuth.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<?php

namespace numelion\jwt;

use yii\di\Instance;
use yii\filters\auth\AuthMethod;

/**
* JwtHttpBearerAuth is an action filter that supports the authentication method based on JSON Web Token.
*
* You may use JwtHttpBearerAuth by attaching it as a behavior to a controller or module, like the following:
*
* ```php
* public function behaviors()
* {
* return [
* 'bearerAuth' => [
* 'class' => \numelion\jwt\JwtHttpBearerAuth::className(),
* ],
* ];
* }
* ```
*
* @author Dmitriy Demin <[email protected]>
* @since 1.0.0-a
*/
class JwtHttpBearerAuth extends AuthMethod
{

/**
* @var Jwt|string|array the [[Jwt]] object or the application component ID of the [[Jwt]].
*/
public $jwt = 'jwt';

/**
* @var string A "realm" attribute MAY be included to indicate the scope
* of protection in the manner described in HTTP/1.1 [RFC2617]. The "realm"
* attribute MUST NOT appear more than once.
*/
public $realm = 'api';

/**
* @var string Authorization header schema, default 'Bearer'
*/
public $schema = 'Bearer';

/**
* @var callable a PHP callable that will authenticate the user with the JWT payload information
*
* ```php
* function ($token, $authMethod) {
* return \app\models\User::findOne($token->getClaim('id'));
* }
* ```
*
* If this property is not set, the username information will be considered as an access token
* while the password information will be ignored. The [[\yii\web\User::loginByAccessToken()]]
* method will be called to authenticate and login the user.
*/
public $auth;

/**
* @inheritdoc
*/
public function init()
{
parent::init();
$this->jwt = Instance::ensure($this->jwt, Jwt::className());
}

/**
* @inheritdoc
*/
public function authenticate($user, $request, $response)
{
$authHeader = $request->getHeaders()->get('Authorization');
if ($authHeader !== null && preg_match('/^' . $this->schema . '\s+(.*?)$/', $authHeader, $matches)) {
$token = $this->loadToken($matches[1]);
if ($token === null) {
return null;
}

if ($this->auth) {
$identity = call_user_func($this->auth, $token, get_class($this));
} else {
$identity = $user->loginByAccessToken($token, get_class($this));
}

return $identity;
}

return null;
}

/**
* @inheritdoc
*/
public function challenge($response)
{
$response->getHeaders()->set(
'WWW-Authenticate',
"{$this->schema} realm=\"{$this->realm}\", error=\"invalid_token\", error_description=\"The access token invalid or expired\""
);
}

/**
* Parses the JWT and returns a token class
* @param string $token JWT
* @return Token|null
*/
public function loadToken($token)
{
return $this->jwt->loadToken($token);
}
}
Loading

0 comments on commit 11eaeee

Please sign in to comment.