Skip to content

Commit

Permalink
Merge pull request #65 from hosannahighertech/master
Browse files Browse the repository at this point in the history
Add JWT Support
  • Loading branch information
filsh committed Jan 4, 2016
2 parents b0557dd + 8227705 commit 6d4b6be
Show file tree
Hide file tree
Showing 2 changed files with 171 additions and 118 deletions.
216 changes: 98 additions & 118 deletions Module.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,197 +3,177 @@
namespace filsh\yii2\oauth2server;

use \Yii;
use yii\i18n\PhpMessageSource;

/**
* For example,
*
* ```php
* 'oauth2' => [
* 'class' => 'filsh\yii2\oauth2server\Module',
* 'options' => [
* 'token_param_name' => 'accessToken',
* 'access_lifetime' => 3600
* ],
* 'tokenParamName' => 'accessToken',
* 'tokenAccessLifetime' => 3600 * 24,
* 'storageMap' => [
* 'user_credentials' => 'common\models\User'
* 'user_credentials' => 'common\models\User',
* ],
* 'grantTypes' => [
* 'client_credentials' => [
* 'class' => '\OAuth2\GrantType\ClientCredentials',
* 'allow_public_clients' => false
* ],
* 'user_credentials' => [
* 'class' => '\OAuth2\GrantType\UserCredentials'
* 'class' => 'OAuth2\GrantType\UserCredentials',
* ],
* 'refresh_token' => [
* 'class' => '\OAuth2\GrantType\RefreshToken',
* 'class' => 'OAuth2\GrantType\RefreshToken',
* 'always_issue_new_refresh_token' => true
* ]
* ],
* ]
* ]
* ```
*/
class Module extends \yii\base\Module
{
public $options = [];
const VERSION = '2.0.0';

public $storageMap = [];
/**
* @var array Model's map
*/
public $modelMap = [];

public $storageDefault = 'filsh\yii2\oauth2server\storage\Pdo';
/**
* @var array Storage's map
*/
public $storageMap = [];

/**
* @var array GrantTypes collection
*/
public $grantTypes = [];

public $modelClasses = [];

public $i18n;

private $_server;

private $_request;
/**
* @var string name of access token parameter
*/
public $tokenParamName;

private $_models = [];
/**
* @var type max access lifetime
*/
public $tokenAccessLifetime;
/**
* @var whether to use JWT tokens
*/
public $useJwtToken = false;//ADDED

/**
* @inheritdoc
*/
public function init()
{
parent::init();
$this->modelClasses = array_merge($this->getDefaultModelClasses(), $this->modelClasses);
$this->registerTranslations();
}

/**
* Get oauth2 server instance
* @param type $force
* @return \OAuth2\Server
* Gets Oauth2 Server
*
* @return \filsh\yii2\oauth2server\Server
* @throws \yii\base\InvalidConfigException
*/
public function getServer($force = false)
public function getServer()
{
if($this->_server === null || $force === true) {
$storages = $this->createStorages();
$server = new \OAuth2\Server($storages, $this->options);
if(!$this->has('server')) {
$storages = [];

if($this->useJwtToken)
{
if(!isset(storageMap['access_token']) || storageMap['public_key']) {
throw new \yii\base\InvalidConfigException('access_token and public_key must be set or set useJwtToken to false');
}
//define dependencies when JWT is used instead of normal token
\Yii::$container->clear('public_key'); //remove old definition
\Yii::$container->set('public_key', $this->storageMap['public_key']);
\Yii::$container->set('OAuth2\Storage\PublicKeyInterface', $this->storageMap['public_key']);

\Yii::$container->clear('access_token'); //remove old definition
\Yii::$container->set('access_token', $this->storageMap['access_token']);
}

foreach(array_keys($this->storageMap) as $name) {
$storages[$name] = \Yii::$container->get($name);
}

$grantTypes = [];
foreach($this->grantTypes as $name => $options) {
if(!isset($storages[$name]) || empty($options['class'])) {
throw new \yii\base\InvalidConfigException('Invalid grant types configuration.');
}

$class = $options['class'];
unset($options['class']);

$reflection = new \ReflectionClass($class);
$config = array_merge([0 => $storages[$name]], [$options]);

$instance = $reflection->newInstanceArgs($config);
$server->addGrantType($instance);
$grantTypes[$name] = $instance;
}

$this->_server = $server;
$server = \Yii::$container->get(Server::className(), [
$this,
$storages,
[
'use_jwt_access_tokens' => $this->useJwtToken,//ADDED
'token_param_name' => $this->tokenParamName,
'access_lifetime' => $this->tokenAccessLifetime,
/** add more ... */
],
$grantTypes
]);

$this->set('server', $server);
}
return $this->_server;
}

/**
* Get oauth2 request instance from global variables
* @return \OAuth2\Request
*/
public function getRequest($force = false)
{
if ($this->_request === null || $force) {
$this->_request = \OAuth2\Request::createFromGlobals();
};
return $this->_request;
return $this->get('server');
}

/**
* Get oauth2 response instance
* @return \OAuth2\Response
*/
public function getResponse()
{
return new \OAuth2\Response();
}

/**
* Create storages
* @return type
*/
public function createStorages()
public function getRequest()
{
$connection = Yii::$app->getDb();
if(!$connection->getIsActive()) {
$connection->open();
if(!$this->has('request')) {
$this->set('request', Request::createFromGlobals());
}

$storages = [];
foreach($this->storageMap as $name => $storage) {
$storages[$name] = Yii::createObject($storage);
}

$defaults = [
'access_token',
'authorization_code',
'client_credentials',
'client',
'refresh_token',
'user_credentials',
'public_key',
'jwt_bearer',
'scope',
];
foreach($defaults as $name) {
if(!isset($storages[$name])) {
$storages[$name] = Yii::createObject($this->storageDefault);
}
}

return $storages;
return $this->get('request');
}

/**
* Get object instance of model
* @param string $name
* @param array $config
* @return ActiveRecord
*/
public function model($name, $config = [])
public function getResponse()
{
if(!isset($this->_models[$name])) {
$className = $this->modelClasses[ucfirst($name)];
$this->_models[$name] = Yii::createObject(array_merge(['class' => $className], $config));
if(!$this->has('response')) {
$this->set('response', new Response());
}
return $this->_models[$name];
return $this->get('response');
}

/**
* Register translations for this module
*
* @return array
*/
public function registerTranslations()
{
Yii::setAlias('@oauth2server', dirname(__FILE__));
if (empty($this->i18n)) {
$this->i18n = [
'class' => 'yii\i18n\PhpMessageSource',
'basePath' => '@oauth2server/messages',
if(!isset(Yii::$app->get('i18n')->translations['modules/oauth2/*'])) {
Yii::$app->get('i18n')->translations['modules/oauth2/*'] = [
'class' => PhpMessageSource::className(),
'basePath' => __DIR__ . '/messages',
];
}
Yii::$app->i18n->translations['oauth2server'] = $this->i18n;
}

/**
* Get default model classes
* @return array
* Translate module message
*
* @param string $category
* @param string $message
* @param array $params
* @param string $language
* @return string
*/
protected function getDefaultModelClasses()
public static function t($category, $message, $params = [], $language = null)
{
return [
'Clients' => 'filsh\yii2\oauth2server\models\OauthClients',
'AccessTokens' => 'filsh\yii2\oauth2server\models\OauthAccessTokens',
'AuthorizationCodes' => 'filsh\yii2\oauth2server\models\OauthAuthorizationCodes',
'RefreshTokens' => 'filsh\yii2\oauth2server\models\OauthRefreshTokens',
'Scopes' => 'filsh\yii2\oauth2server\models\OauthScopes',
];
return Yii::t('modules/oauth2/' . $category, $message, $params, $language);
}
}
73 changes: 73 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,80 @@ To use this extension, simply add the following code in your application config
]
],
```
If you want to get Json Web Token (JWT) instead of convetional token, you will need to set `'useJwtToken' => true` in module and then define two more configurations:
`'public_key' => 'app\storage\PublicKeyStorage'` which is the class that implements [PublickKeyInterface](https://github.com/bshaffer/oauth2-server-php/blob/develop/src/OAuth2/Storage/PublicKeyInterface.php) and `'access_token' => 'app\storage\JwtAccessToken'` which implements [JwtAccessTokenInterface.php](https://github.com/bshaffer/oauth2-server-php/blob/develop/src/OAuth2/Storage/JwtAccessTokenInterface.php)

For Oauth2 base library provides the default [access_token](https://github.com/bshaffer/oauth2-server-php/blob/develop/src/OAuth2/Storage/JwtAccessToken.php) which works great except that it tries to save the token in the database. So I decided to inherit from it and override the part that tries to save (token size is too big and crashes with VARCHAR(40) in the database.

TL;DR, here are the sample classes
**access_token**
```php
<?php

namespace app\storage;

/**
*
* @author Stefano Mtangoo <mwinjilisti at gmail dot com>
*/
class JwtAccessToken extends \OAuth2\Storage\JwtAccessToken
{
public function setAccessToken($oauth_token, $client_id, $user_id, $expires, $scope = null)
{

}

public function unsetAccessToken($access_token)
{

}
}

```

and **public_key**

```php
<?php
namespace app\storage;

class PublicKeyStorage implements \OAuth2\Storage\PublicKeyInterface{


private $pbk = null;
private $pvk = null;

public function __construct()
{
//files should be in same directory as this file
//keys can be generated using OpenSSL tool with command:
/*
private key:
openssl genrsa -out privkey.pem 2048

public key:
openssl rsa -in privkey.pem -pubout -out pubkey.pem
*/
$this->pbk = file_get_contents('privkey.pem', true);
$this->pvk = file_get_contents('pubkey.pem', true);
}

public function getPublicKey($client_id = null){
return $this->pbk;
}

public function getPrivateKey($client_id = null){
return $this->pvk;
}

public function getEncryptionAlgorithm($client_id = null){
return 'HS256';
}

}

```
**NOTE:** You will need [this](https://github.com/bshaffer/oauth2-server-php/pull/690) PR applied or you can patch it yourself by checking changes in [this diff](https://github.com/hosannahighertech/oauth2-server-php/commit/ec79732663547065c041e279109137a423eac0cb). The other part of PR is only if you want to use firebase JWT library (which is not mandatory anyway).

Also, extend ```common\models\User``` - user model - implementing the interface ```\OAuth2\Storage\UserCredentialsInterface```, so the oauth2 credentials data stored in user table.
You should implement:
Expand Down

17 comments on commit 6d4b6be

@manoelt
Copy link

@manoelt manoelt commented on 6d4b6be Jan 5, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@filsh @mtangoo

This is throwing exception on line:

        foreach(array_keys($this->storageMap) as $name) {
            $storages[$name] = \Yii::$container->get($name);
        }

Trying to get $name = 'user_credentials' from the container.

Exception:

"message": "Class user_credentials does not exist",

@mtangoo
Copy link
Contributor

@mtangoo mtangoo commented on 6d4b6be Jan 5, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do you install? Please post full error.

@mtangoo
Copy link
Contributor

@mtangoo mtangoo commented on 6d4b6be Jan 5, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@manoelt
Copy link

@manoelt manoelt commented on 6d4b6be Jan 5, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mtangoo, what exactly do you want to know?

It is installed via composer and was working fine until today after update.

@mtangoo
Copy link
Contributor

@mtangoo mtangoo commented on 6d4b6be Jan 5, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you post:

  1. Full error stack
  2. Module. php
  3. Relevant part of config file

@manoelt
Copy link

@manoelt manoelt commented on 6d4b6be Jan 5, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stack:

{
  "name": "Exception",
  "message": "Class user_credentials does not exist",
  "code": -1,
  "type": "ReflectionException",
  "file": "/Users/admin/Sites/elo-jur-dico-web/vendor/yiisoft/yii2/di/Container.php",
  "line": 415,
  "stack-trace": [
    "#0 /Users/admin/Sites/elo-jur-dico-web/vendor/yiisoft/yii2/di/Container.php(415): ReflectionClass->__construct('user_credential...')",
    "#1 /Users/admin/Sites/elo-jur-dico-web/vendor/yiisoft/yii2/di/Container.php(358): yii\di\Container->getDependencies('user_credential...')",
    "#2 /Users/admin/Sites/elo-jur-dico-web/vendor/yiisoft/yii2/di/Container.php(151): yii\di\Container->build('user_credential...', Array, Array)",
    "#3 /Users/admin/Sites/elo-jur-dico-web/vendor/filsh/yii2-oauth2-server/Module.php(100): yii\di\Container->get('user_credential...')",
    "#4 /Users/admin/Sites/elo-jur-dico-web/vendor/filsh/yii2-oauth2-server/filters/auth/CompositeAuth.php(14): filsh\yii2\oauth2server\Module->getServer()",
    "#5 /Users/admin/Sites/elo-jur-dico-web/common/classes/CompositeAuth.php(15): filsh\yii2\oauth2server\filters\auth\CompositeAuth->beforeAction(Object(yii\base\InlineAction))",
    "#6 /Users/admin/Sites/elo-jur-dico-web/vendor/yiisoft/yii2/base/ActionFilter.php(70): common\classes\CompositeAuth->beforeAction(Object(yii\base\InlineAction))",
    "#7 [internal function]: yii\base\ActionFilter->beforeFilter(Object(yii\base\ActionEvent))",
    "#8 /Users/admin/Sites/elo-jur-dico-web/vendor/yiisoft/yii2/base/Component.php(541): call_user_func(Array, Object(yii\base\ActionEvent))",
    "#9 /Users/admin/Sites/elo-jur-dico-web/vendor/yiisoft/yii2/base/Controller.php(269): yii\base\Component->trigger('beforeAction', Object(yii\base\ActionEvent))",
    "#10 /Users/admin/Sites/elo-jur-dico-web/vendor/yiisoft/yii2/web/Controller.php(121): yii\base\Controller->beforeAction(Object(yii\base\InlineAction))",
    "#11 /Users/admin/Sites/elo-jur-dico-web/frontend/controllers/api/v1/EloRestController.php(44): yii\web\Controller->beforeAction(Object(yii\base\InlineAction))",
    "#12 /Users/admin/Sites/elo-jur-dico-web/vendor/yiisoft/yii2/base/Controller.php(152): frontend\controllers\api\v1\EloRestController->beforeAction(Object(yii\base\InlineAction))",
    "#13 /Users/admin/Sites/elo-jur-dico-web/vendor/yiisoft/yii2/base/Module.php(454): yii\base\Controller->runAction('escritorio', Array)",
    "#14 /Users/admin/Sites/elo-jur-dico-web/vendor/yiisoft/yii2/web/Application.php(84): yii\base\Module->runAction('api/v1/escritor...', Array)",
    "#15 /Users/admin/Sites/elo-jur-dico-web/vendor/yiisoft/yii2/base/Application.php(375): yii\web\Application->handleRequest(Object(yii\web\Request))",
    "#16 /Users/admin/Sites/elo-jur-dico-web/frontend/web/index.php(18): yii\base\Application->run()",
    "#17 {main}"
  ]
}

Module is the last version.

Config:

'modules' => [
        'oauth2' => [
            'class' => 'filsh\yii2\oauth2server\Module',
           'tokenParamName' => 'accessToken',
           'tokenAccessLifetime' => 3600 * 24,
            'storageMap' => [
                'user_credentials' => 'common\models\UserOAuth'
            ],
            'grantTypes' => [
                'client_credentials' => [
                    'class' => 'OAuth2\GrantType\ClientCredentials',
                    'allow_public_clients' => false
                ],
                'user_credentials' => [
                    'class' => 'OAuth2\GrantType\UserCredentials'
                ],
                'refresh_token' => [
                    'class' => 'OAuth2\GrantType\RefreshToken',
                    'always_issue_new_refresh_token' => true
                ]
            ]
        ]
    ],

@manoelt
Copy link

@manoelt manoelt commented on 6d4b6be Jan 5, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mtangoo

Could you explain why are you getting 'user_credentials' from container? Where this was set on the container?

@mtangoo
Copy link
Contributor

@mtangoo mtangoo commented on 6d4b6be Jan 5, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's my reason to ask tgat you post Module which unfortunately you didn't

@mtangoo
Copy link
Contributor

@mtangoo mtangoo commented on 6d4b6be Jan 5, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also I can see you define User credentials grant bur nowhere you define its respective Storage (the user class)
Please see the readme again

@manoelt
Copy link

@manoelt manoelt commented on 6d4b6be Jan 5, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mtangoo

As I said, the Module is the same as https://github.com/Filsh/yii2-oauth2-server/blob/master/Module.php

What are you saying about "respective Storage"?

'storageMap' => [
                'user_credentials' => 'common\models\UserOAuth'
            ],

@mtangoo
Copy link
Contributor

@mtangoo mtangoo commented on 6d4b6be Jan 5, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. I see about config. Unfortunately I can't help without posting YOUR Module file. Though you say its tge same as that one I doubt it's the case

@manoelt
Copy link

@manoelt manoelt commented on 6d4b6be Jan 5, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LOL. So I am posting my Module, whitch is the same, as I said. So now please don't say that I am lying. :)

<?php

namespace filsh\yii2\oauth2server;

use \Yii;
use yii\i18n\PhpMessageSource;
use  \array_key_exists;

/**
 * For example,
 * 
 * ```php
 * 'oauth2' => [
 *     'class' => 'filsh\yii2\oauth2server\Module',
 *     'tokenParamName' => 'accessToken',
 *     'tokenAccessLifetime' => 3600 * 24,
 *     'storageMap' => [
 *         'user_credentials' => 'common\models\User',
 *     ],
 *     'grantTypes' => [
 *         'user_credentials' => [
 *             'class' => 'OAuth2\GrantType\UserCredentials',
 *         ],
 *         'refresh_token' => [
 *             'class' => 'OAuth2\GrantType\RefreshToken',
 *             'always_issue_new_refresh_token' => true
 *         ]
 *     ]
 * ]
 * ```
 */
class Module extends \yii\base\Module
{
    const VERSION = '2.0.0';

    /**
     * @var array Model's map
     */
    public $modelMap = [];

    /**
     * @var array Storage's map
     */
    public $storageMap = [];

    /**
     * @var array GrantTypes collection
     */
    public $grantTypes = [];

    /**
     * @var string name of access token parameter
     */
    public $tokenParamName;

    /**
     * @var type max access lifetime
     */
    public $tokenAccessLifetime;
    /**
     * @var whether to use JWT tokens
     */
    public $useJwtToken = false;//ADDED

    /**
     * @inheritdoc
     */
    public function init()
    {
        parent::init();
        $this->registerTranslations();
    }

    /**
     * Gets Oauth2 Server
     * 
     * @return \filsh\yii2\oauth2server\Server
     * @throws \yii\base\InvalidConfigException
     */
    public function getServer()
    {
        if(!$this->has('server')) {
            $storages = [];

            if($this->useJwtToken)
            {
                if(!array_key_exists('access_token', $this->storageMap) || !array_key_exists('public_key', $this->storageMap)) {
                        throw new \yii\base\InvalidConfigException('access_token and public_key must be set or set useJwtToken to false');
                }
                //define dependencies when JWT is used instead of normal token
                \Yii::$container->clear('public_key'); //remove old definition
                \Yii::$container->set('public_key', $this->storageMap['public_key']);
                \Yii::$container->set('OAuth2\Storage\PublicKeyInterface', $this->storageMap['public_key']);

                \Yii::$container->clear('access_token'); //remove old definition
                \Yii::$container->set('access_token', $this->storageMap['access_token']);
            }

            foreach(array_keys($this->storageMap) as $name) {
                $storages[$name] = \Yii::$container->get($name);
            }

            $grantTypes = [];
            foreach($this->grantTypes as $name => $options) {
                if(!isset($storages[$name]) || empty($options['class'])) {
                    throw new \yii\base\InvalidConfigException('Invalid grant types configuration.');
                }

                $class = $options['class'];
                unset($options['class']);

                $reflection = new \ReflectionClass($class);
                $config = array_merge([0 => $storages[$name]], [$options]);

                $instance = $reflection->newInstanceArgs($config);
                $grantTypes[$name] = $instance;
            }

            $server = \Yii::$container->get(Server::className(), [
                $this,
                $storages,
                [
                    'use_jwt_access_tokens' => $this->useJwtToken,//ADDED
                    'token_param_name' => $this->tokenParamName,
                    'access_lifetime' => $this->tokenAccessLifetime,
                    /** add more ... */
                ],
                $grantTypes
            ]);

            $this->set('server', $server);
        }
        return $this->get('server');
    }

    public function getRequest()
    {
        if(!$this->has('request')) {
            $this->set('request', Request::createFromGlobals());
        }
        return $this->get('request');
    }

    public function getResponse()
    {
        if(!$this->has('response')) {
            $this->set('response', new Response());
        }
        return $this->get('response');
    }

    /**
     * Register translations for this module
     * 
     * @return array
     */
    public function registerTranslations()
    {
        if(!isset(Yii::$app->get('i18n')->translations['modules/oauth2/*'])) {
            Yii::$app->get('i18n')->translations['modules/oauth2/*'] = [
                'class'    => PhpMessageSource::className(),
                'basePath' => __DIR__ . '/messages',
            ];
        }
    }

    /**
     * Translate module message
     * 
     * @param string $category
     * @param string $message
     * @param array $params
     * @param string $language
     * @return string
     */
    public static function t($category, $message, $params = [], $language = null)
    {
        return Yii::t('modules/oauth2/' . $category, $message, $params, $language);
    }
}

@manoelt
Copy link

@manoelt manoelt commented on 6d4b6be Jan 5, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@filsh

Is this version working fine for you?

I tried 2.0.1 but I need a change that I suggested (table_prefix) and is not on that version. :(

@mtangoo
Copy link
Contributor

@mtangoo mtangoo commented on 6d4b6be Jan 5, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well can you check what was added to Server. php and bootstrap files prior to two PR that I created? I must have added something there

@manoelt
Copy link

@manoelt manoelt commented on 6d4b6be Jan 5, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mtangoo Why did you remove the method public function createStorages()?

@mtangoo
Copy link
Contributor

@mtangoo mtangoo commented on 6d4b6be Jan 5, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No I didn't

@manoelt
Copy link

@manoelt manoelt commented on 6d4b6be Jan 5, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

db390f0

Here it is. You removed the method.

Please sign in to comment.