yii2-oauth2-server
yii2-oauth2-server copied to clipboard
How to use this extension with official Yii2 AuthClient ?
I have 2 apps:
- Yii2 server (wtih this exxtension itself)
- Yii2 client (With https://github.com/yiisoft/yii2-authclient )
Everything is looked good in every step at Yii2 server side:
'components' => [
'urlManager' => [
'enablePrettyUrl' => true,
'showScriptName' => false,
'rules' => [
'<alias:\w+>' => 'site/<alias>',
'POST oauth2/<action:\w+>' => 'oauth2/rest/<action>',
]
],
],
'modules' => [
'oauth2' => [
'class' => 'filsh\yii2\oauth2server\Module',
'components' => [
'request' => function () {
return \filsh\yii2\oauth2server\Request::createFromGlobals();
},
'response' => [
'class' => \filsh\yii2\oauth2server\Response::class,
],
],
'tokenParamName' => 'accessToken',
'tokenAccessLifetime' => 3600 * 24,
'storageMap' => [
'user_credentials' => 'app\models\User',
],
'grantTypes' => [
'user_credentials' => [
'class' => 'OAuth2\GrantType\UserCredentials',
],
'authorization_code' => [
'class' => 'OAuth2\GrantType\AuthorizationCode'
],
'refresh_token' => [
'class' => 'OAuth2\GrantType\RefreshToken',
//'always_issue_new_refresh_token' => true
]
],
'options' => [
'allow_implicit' => true
]
],
....
But the problem is, when client side successfully login, why userAttributes is null ? In Yii2 client side, I set the SiteController like this:
/**
* {@inheritdoc}
*/
public function actions()
{
return [
'error' => [
'class' => 'yii\web\ErrorAction',
],
'captcha' => [
'class' => 'yii\captcha\CaptchaAction',
'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null,
],
'auth' => [
'class' => 'yii\authclient\AuthAction',
'successCallback' => [$this, 'onAuthSuccess'],
],
];
}
public function onAuthSuccess($client)
{
(new AuthHandler($client))->handle();
}
Then, in AuthHandler:
<?php
namespace app\components;
use app\models\Auth;
use app\models\User;
use Exception;
use Yii;
use yii\authclient\ClientInterface;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;
use yii\helpers\StringHelper;
use yii\helpers\VarDumper;
/**
* AuthHandler handles successful authentication via Yii auth component
*/
class AuthHandler
{
/**
* @var ClientInterface
*/
private ClientInterface $client;
public function __construct(ClientInterface $client)
{
$this->client = $client;
}
/**
* @return void
* @throws \yii\base\Exception
* @throws \yii\db\Exception
* @throws Exception
*/
public function handle()
{
$attributes = $this->client->getUserAttributes();
/*
* for debugging
* die(Html::tag('pre', VarDumper::dumpAsString($attributes)));
* */
$email = ArrayHelper::getValue($attributes, 'email');
$id = ArrayHelper::getValue($attributes, 'id');
$nickname = str_replace(" ", "-", ArrayHelper::getValue($attributes, 'name'));
/* @var Auth $auth */
$auth = Auth::find()->where([
'source' => $this->client->getId(),
'source_id' => $id,
])->one();
if (Yii::$app->user->isGuest) {
if ($auth) { // login
$user = $auth->user;
$this->updateUserInfo($user);
Yii::$app->user->login($user, Yii::$app->params['user.rememberMeDuration']);
Yii::$app->getSession()->setFlash(
'success',
'Login by ' . ucfirst($this->client->getId()) . ' Welcome! '
);
} else { // signup
if ($email !== null && User::find()->where(['email' => $email])->exists()) {
Yii::$app->getSession()->setFlash('error', [
Yii::t('app', "User with the same email as in {client} account already exists but isn't linked to it. Login using email first to link it.", ['client' => $this->client->getTitle()]),
]);
} else {
$password = Yii::$app->security->generateRandomString(Yii::$app->params['user.passwordMinLength']);
$user = new User([
'username' => $nickname,
//'github' => $nickname,
'email' => $email,
'password' => $password,
]);
$user->generateAuthKey();
$user->generatePasswordResetToken();
$transaction = User::getDb()->beginTransaction();
if ($user->save()) {
$auth = new Auth([
'user_id' => $user->id,
'source' => $this->client->getId(),
'source_id' => (string)$id,
]);
if ($auth->save()) {
$transaction->commit();
Yii::$app->getSession()->setFlash(
'success',
'Joined as ' . ucfirst($this->client->getId()) . ', and welcome...! '
);
Yii::$app->user->login($user, Yii::$app->params['user.rememberMeDuration']);
} else {
Yii::$app->getSession()->setFlash('error', [
Yii::t('app', 'Unable to save {client} account: {errors}', [
'client' => $this->client->getTitle(),
'errors' => yii\helpers\Json::encode($auth->getErrors()),
]),
]);
}
} else {
Yii::$app->getSession()->setFlash('error', [
Yii::t('app', 'Unable to save user: {errors}', [
'client' => $this->client->getTitle(),
'errors' => yii\helpers\Json::encode($user->getErrors()),
]),
]);
}
}
}
} else { // user already logged in
if (!$auth) { // add auth provider
$auth = new Auth([
'user_id' => Yii::$app->user->id,
'source' => $this->client->getId(),
'source_id' => (string)$attributes['id'],
]);
if ($auth->save()) {
$user = $auth->user;
$this->updateUserInfo($user);
Yii::$app->getSession()->setFlash('success', [
Yii::t('app', 'Linked {client} account.', [
'client' => $this->client->getTitle()
]),
]);
} else {
Yii::$app->getSession()->setFlash('error', [
Yii::t('app', 'Unable to link {client} account: {errors}', [
'client' => $this->client->getTitle(),
'errors' => yii\helpers\Json::encode($auth->getErrors()),
]),
]);
}
} else { // there's existing auth
Yii::$app->getSession()->setFlash('error', [
Yii::t(
'app',
'Unable to link {client} account. There is another user using it.',
['client' => $this->client->getTitle()]
),
]);
}
}
}
/**
* @param User $user
* @return void
* @throws Exception
*/
private function updateUserInfo(User $user)
{
$attributes = $this->client->getUserAttributes();
$github = ArrayHelper::getValue($attributes, 'login');
if ($github) {
if ($user->github === null) {
$user->github = $github;
$user->save();
}
}
}
}
Here is the config that I used in Yii2 client side:
<?php
namespace app\components;
use yii\authclient\OAuth2;
class MyAuthClient extends OAuth2
{
protected function defaultName()
{
return 'my_auth_client';
}
protected function defaultTitle()
{
return 'My Auth Client';
}
protected function initUserAttributes()
{
return $this->api('userinfo', 'GET');
}
}
'components' => [
'authClientCollection' => [
'class' => 'yii\authclient\Collection',
'clients' => [
'myauth' => [
'class' => '\app\components\MyAuthClient',
'clientId' => 'testclient',
'clientSecret' => 'testpass',
'authUrl' => 'http://10.60.36.60:8080/authorize',
'tokenUrl' => 'http://10.60.36.60:8080/oauth2/token',
'apiBaseUrl' => 'http://10.60.36.60:8080/apis/oauth2/v1'
],
],
],
....
And, Here is the info:
app\components\MyAuthClient#1
(
[yii\base\Component:_events] => []
[yii\base\Component:_eventWildcards] => []
[yii\base\Component:_behaviors] => null
[yii\authclient\BaseClient:_id] => 'myauth'
[yii\authclient\BaseClient:_name] => null
[yii\authclient\BaseClient:_title] => null
[yii\authclient\BaseClient:_userAttributes] => null
[yii\authclient\BaseClient:_normalizeUserAttributeMap] => null
[yii\authclient\BaseClient:_viewOptions] => null
[yii\authclient\BaseClient:_httpClient] => yii\httpclient\Client#2
(
[yii\base\Component:_events] => []
[yii\base\Component:_eventWildcards] => []
[yii\base\Component:_behaviors] => []
[baseUrl] => 'http://10.60.36.60:8080/apis/oauth2/v1'
[formatters] => [
'urlencoded' => yii\httpclient\UrlEncodedFormatter#3
(
[encodingType] => 1
[charset] => null
)
]
[parsers] => [
'json' => yii\httpclient\JsonParser#4
(
[asArray] => true
)
]
[requestConfig] => []
[responseConfig] => []
[contentLoggingMaxSize] => 2000
[yii\httpclient\Client:_transport] => yii\httpclient\StreamTransport#5
(
[yii\base\Component:_events] => []
[yii\base\Component:_eventWildcards] => []
[yii\base\Component:_behaviors] => null
)
)
[yii\authclient\BaseClient:_requestOptions] => []
[yii\authclient\BaseClient:_stateStorage] => yii\authclient\SessionStateStorage#6
(
[yii\base\Component:_events] => []
[yii\base\Component:_eventWildcards] => []
[yii\base\Component:_behaviors] => null
[session] => yii\web\Session#7
(
[yii\base\Component:_events] => []
[yii\base\Component:_eventWildcards] => []
[yii\base\Component:_behaviors] => null
[flashParam] => '__flash'
[handler] => null
[*:_forceRegenerateId] => null
[yii\web\Session:_cookieParams] => [
'httponly' => true
]
[yii\web\Session:frozenSessionData] => null
[yii\web\Session:_hasSessionId] => true
)
)
[version] => '2.0'
[apiBaseUrl] => 'http://10.60.36.60:8080/apis/oauth2/v1'
[authUrl] => 'http://10.60.36.60:8080/authorize'
[scope] => null
[autoRefreshAccessToken] => true
[parametersToKeepInReturnUrl] => [
0 => 'authclient'
]
[yii\authclient\BaseOAuth:_returnUrl] => 'http://10.60.36.60:8081/auth?authclient=myauth'
[yii\authclient\BaseOAuth:_accessToken] => yii\authclient\OAuthToken#8
(
[tokenParamKey] => 'access_token'
[tokenSecretParamKey] => 'oauth_token_secret'
[createTimestamp] => 1670846067
[yii\authclient\OAuthToken:_expireDurationParamKey] => null
[yii\authclient\OAuthToken:_params] => [
'access_token' => '358d8f2831a027f9e815a5b44f569678445a9429'
'expires_in' => 86400
'token_type' => 'Bearer'
'scope' => null
'refresh_token' => 'da3f5c8c33cd694479ee4a4fb51ccdd7943cf478'
]
)
[yii\authclient\BaseOAuth:_signatureMethod] => []
[clientId] => 'testclient'
[clientSecret] => 'testpass'
[tokenUrl] => 'http://10.60.36.60:8080/oauth2/token'
[validateAuthState] => true
[enablePkce] => false
)
Maybe we need, how to implement the OpenID ?