Yii2 - switch response between rest (json) and normal html output - php

I need a way to active rest when some one using ajax X-Requested-With
and deactivate that when doesn't. with this way I can handle search engine
or users without ajax.(improve SEO)
some research: (using behaviors)
public function behaviors()
{
return [
'verbs' => [
'class' => \yii\web\ResponseFilter::className(),
'actions' => [
'something' => [
'format' => Response::FORMAT_JSON,
],
],
],
];
}
check request headers and change response.
or: (edit rest controller)
edit $serializer = 'yii\rest\Serializer'; to $serializer = null;
or (override after action of rest controller)
public function afterAction($action, $result)
{
$result = parent::afterAction($action, $result);
return $this->serializeData($result);
}
and remove serializeData() some how...
but what is the ultimate way?
I think these ways are not fine...

Related

Setting Routes for RestApi in Yii2

I am trying to make rest api with my methods.
'rules' => [
[
'class' => 'yii\rest\UrlRule',
'controller' => ['ApiController'],
'patterns' => [
'PUT,PATCH api/{id}/update' => 'update',
'DELETE api/{id}/delete' => 'delete',
'GET,HEAD api/{id}' => 'get',
'POST api/{id}/create' => 'create',
'GET,HEAD' => 'api/index',
'{id}' => 'options',
'' => 'options',
]
],
Api controller:
/**
* Displays homepage.
*
* #return string
*/
public function actionIndex()
{
// $id = Yii::$app->request->getQueryParam("id"); //
Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
return "ok";
}
/**
* Displays homepage.
*
* #return string
*/
public function actionGet($id)
{
// $id = Yii::$app->request->getQueryParam("id"); //
Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
return "get";
}
Url api returns index action, but url api/1 doesn't return get action.
How to configure routing?
If you are ok with the default actions provided by yii you can simplify your code quite a bit to make it work.
Configure the response type on the application configuration, then you won't need to do it in each method.
Remove the 'patterns' element from your rules, yii automatically matches the patterns that you are trying to use.
Decide if you want to pluralize your rules or not, if you don't want to pluralize them you need to add 'pluralize' => false to your configuration rules.
web.config
// configure json response globally
'response' => [
'format' => Response::FORMAT_JSON,
'formatters' => [
Response::FORMAT_JSON => [
'class' => '\yii\web\JsonResponseFormatter',
'prettyPrint' => YII_DEBUG,
'encodeOptions' => JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE,
]
],
],
// Add ApiController rules
'rules' => [
[
'class' => 'yii\rest\UrlRule',
'controller' => 'api',
// uncoment the next line if you want to request '/api/2' instead of '/apis/2'
// 'pluralize' => false
],
// ... more rules here
]
ApiController
<?php
namespace app\controllers;
use yii\rest\Controller;
class ApiController extends Controller
{
public function actionIndex()
{
return 'Api index action';
}
public function actionView($id)
{
return "Get item $id";
}
}
Using the configuration provided you can request the index route sending a GET request to the /apis endpoint, to control the result customize actionIndex, you can provide a dataProvider as the response and the formatter element will deal with it correctly.
To request one element of the collection, send a GET request to the /apis/5 endpoint, where 5 is just an example $id, if you return a model, the formatter will deal with it using the fields attribute of the model.
If you want to use endpoints like in your question, i.e. without the plural form, uncomment the pluralize line on the example, the endpoints will be /api and /api/5.
There are multiple examples of this on the official documentation, the quick start and building a REST API pages make for some good reading and are packed with examples.
Personally I would recommend not naming a controller ApiController, it seems confusing, your API probably has api already on the url so you will end up with urls like https://api.mydomain.com/api/5

Yii2: Simultaneous authentication between frontend and API

I've turned the backend into an API. And the API controllers have HttpBasicAuth type authentication.
The problem is that even after authentication in the frontend, whenever a request is made to the API, the authentication window appears.
How can I do so that when the user authenticates in the frontend, is not requested again the username and password of access when a request is made to the API?
Example a controller in API:
class CategoryController extends ActiveController
{
public $modelClass = 'api\models\Category';
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['authenticator'] = [
'class' => CompositeAuth::className(),
'authMethods' => [
[
'class' => HttpBasicAuth::className(),
'auth' => function($username, $password) {
$out = null;
$user = \common\models\User::findByUsername($username);
if ($user != null) {
if ($user->validatePassword($password)) $out = $user;
}
return $out;
}
],
],
];
return $behaviors;
}
}
It is called sharing sessions. It also depends on if your tier apps (frontend and api) are both in the same domain. If it is, configure your frontend and api settings (<app>/frontend/config/main.php and <app>/api/config/main.php) as follow:
'components' => [
...
'request' => [
'csrfParam' => '_csrf-shared',
],
...
'user' => [
'identityClass' => 'common\models\User',
'enableAutoLogin' => true,
'identityCookie' => ['name' => '_identity-shared', 'httpOnly' => true],
],
...
'session' => [
'name' => 'advanced-shared',
],
...
It means you save cookies and session with the same name, so that when you login in frontend, and go to backend/api, the backend side fetches same cookies, therefore you'll be detected as authenticated user.
Here one important note, in order to enableAutoLogin work for both tiers, you should set same cookieValidationKey for both main-local.php settings. You can just set them manually, or edit init.php file to generate one cookieValidationKey for all tiers. (Just make sure you know what you're doing).
By the way, I think it's not a good idea to make simultaneous authentication between frontend and api. If it's frontend and backend then it's still bearable, but api interaction is different compared to frontend.
I suggest to use headers like Authorization: Bearer <token>.. You can get more information about it here Yii2 Rest Authentication
Update
I assume this is what you need.. Create a class, i.e. ApiAuth in common/components folder and paste the following code:
<?php
namespace common\components;
use yii\filters\auth\HttpBasicAuth;
class ApiAuth extends HttpBasicAuth
{
/**
* #inheritdoc
*/
public function authenticate($user, $request, $response)
{
if ($user->identity) {
return $user->identity;
}
return parent::authenticate($user, $request, $response);
}
}
This class extends from yii\filters\auth\HttpBasicAuth. Before calling a browser prompt it checks whether user->dentity is populated. If so, no promt is required.
In your Controller behavior replace HttpBasicAuth with ApiAuth class:
use common\components\ApiAuth;
...
'authMethods' => [
[
'class' => ApiAuth::className(),
'auth' => function($username, $password) {
...
As the user is already authenticated, then I just set the "AccessControl" for connected users. If they are not connected they will receive code 403 instead of 401.
This solves the problem:
class CategoryController extends ActiveController
{
public $modelClass = 'api\models\Category';
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['access'] = [
'class' => \yii\filters\AccessControl::className(),
'rules' => [
[
'allow' => true,
'roles' => ['#'],
],
],
];
return $behaviors;
}
}

Disable built-in authenticator error for unauthorized users Yii2

I have a controller with many actions. Now I want to use token-based authentication so I changed the behavior like this:
public function behaviors()
{
return [
'authenticator' => [
'class' => CompositeAuth::className(),
'only' => [
'logout',
'revoke'
],
'authMethods' => [
HttpBasicAuth::className(),
HttpBearerAuth::className(),
QueryParamAuth::className(),
],
]
];
}
This code works well but there is one problem.
I want to handle the unauthorized users by myself (not Yii) but when there is an unauthorized user send a request to my action, my action doesn't work and it will return Yii's default error.
How can I tell Yii to just authenticate the user (because I want to use Yii::$app->user->isGuest) and don't send default error?
UPDATE: I just want to disable the authenticator errors, I need other errors.
Yii2
PHP 7.2
You may use $optional property to configure actions where authentication should be optional (should not throw error). If it should be optional for all actions, you may use * instead of action name:
public function behaviors()
{
return [
'authenticator' => [
'class' => CompositeAuth::className(),
'optional' => ['*'],
// ...
]
];
}

Yii2 - How to exclude methods from authenticator behaviors

i am building an API with Yii2 framework, i need to tell yii some actions act as public action.
i added except in my controller's behaviors function but its not works
public function behaviors() {
$behaviors = parent::behaviors();
$behaviors['authenticator'] = [
'class' => HttpBearerAuth::className(),
'except' => ['NotifyOrder'],
];
return $behaviors;
}
public function actionNotifyOrder() {
echo 1;
}
i am always getting following error when i call my /notify-order url
<response><name>Unauthorized</name><message>Your request was made with invalid credentials.</message><code>0</code><status>401</status><type>yii\web\UnauthorizedHttpException</type></response>
according to the docs you need to tell it the action IDs (the dash-separated format used in urls)
you should have
$behaviors['authenticator'] = [
'class' => HttpBearerAuth::className(),
'except' => ['notify-order', 'another-action', 'and-so-on'],
];

Yii2: configure yii\web\ViewAction::$viewPrefix

I want to change default directory ("pages") of static pages views, using yii\web\ViewAction. Documentation says:
You may configure yii\web\ViewAction::$viewPrefix to change the
directory for searching these views.
Question: where and how need I to change this property? I tried to do it in controller:
use yii\web\ViewAction;
...
public function actions()
{
(new ViewAction)->viewPrefix = ''; //wanted to do it just as '#app/views/site/', without any subdirectory ('pages' or others)
return [
'stat' => [
'class' => 'yii\web\ViewAction',
],
];
}
But no success.
Try to set viewPrefix to null
public function actions()
{
return [
'stat' => [
'class' => 'yii\web\ViewAction',
'viewPrefix' => null,
],
];
}

Categories