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
Related
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;
}
}
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' => ['*'],
// ...
]
];
}
I am trying to keep my controller clean and move the custom request validation in to a separate class as:
public function register(RegisterUserRequest $request)
and in there define all the usual functions, such as
public function rules(),
public function messages(), and
public function authorize()
However, the frontend is expecting the following data to display:
title (title related to the validation error message), description (which is the the actual validation error message), and status (=red, yellow etc)
How can I actually customise the response of the request?
Something like this, does not seem to be working:
protected function failedValidation(\Illuminate\Contracts\Validation\Validator $validator)
{
$response = new Response(['data' => [],
'meta' => [
'title' => 'Email Invalid'
'description' => '(The error message as being returned right now)',
'status' => 'red'
]);
throw new ValidationException($validator, $response);
}
Any ideas?
you can do this by making request in laravel as the following :
php artisan make:request FailedValidationRequest
this command will create class called FailedValidationRequest in
App\Http\Request directory and you cab write your rules inside this class as the following:
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class FailedValidationRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'title' => 'required'
'description' => 'required',
'status' => 'required|numeric'
];
}
}
and if you want to customize the error message you can download the language you want from this link using composer:
https://packagist.org/packages/caouecs/laravel-lang
and write the language you want by copying the languge folwder to the lang directory in your resource folder.
'custom' => [
'title' => [
'required' => 'your custom message goes here',
],
'decription' => [
'required' => 'your custom message goes here',
],
],
try{
$request->validate([
'aadhar' => 'required|digits:12|numeric',
'name' => 'required|string|max:511',
'dob' => 'required|date_format:Y-m-d',
'email' => 'required|email|max:255',
'address' => 'required|string',
'insuranceid' => 'required|digits_between:1,15|integer',
'password' => 'required|min:59|max:60',
]);
}
catch(Exception $error){
$message = $error->getMessage();
$status_code=400;
return response()->json(["message" => $message,"status_code" => $status_code]);
}
This is my piece of code for the validation of the request parameters sent to an API. The documentation gives details only about custom error messages in case of a form request.
The validation errors give the default message "The given data was invalid" but I would like to know which of the parameter was invalid. How do I give custom validation error messages for API requests validation?
First of all, to decouple your code, you could use a Form Request class. From the docs:
For more complex validation scenarios, you may wish to create a "form
request". Form requests are custom request classes that contain
validation logic.
This class contains two methods:
1 - rules, the place where you specify your rules, it should return an array of rules.
2 - authorize that return a boolean,this method control who is allowed to perform this request. By default is set to false, so every call will be rejected.
So, in your case, it should be something like this:
First, create your custom Request class executing this artisan command in your console:
php artisan make:request CreateCustomObjectRequest
This wil create a new class under app/Http/Requests:
class CreateCustomObjectRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
// Implement here your Auth validation, something like:
return auth()->check();
// or just return "true" if you want to take care of this anywhere else.
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'aadhar' => 'required|digits:12|numeric',
'name' => 'required|string|max:511',
'dob' => 'required|date_format:Y-m-d',
'email' => 'required|email|max:255',
'address' => 'required|string',
'insuranceid' => 'required|digits_between:1,15|integer',
'password' => 'required|min:59|max:60',
];
}
}
And then, in your controller, instead of inject a regular Request object, we are gonna use this custom Request object:
use App\Http\Requests\CreateCustomObjectRequest;
// ...
public function store(CreateCustomObjectRequest $request)
{
// the rest of your controller logic.
}
Now, the part you are really interested in. To return errors in a json way you should add the next header when making a request:
Accept: Application/json
This header will tell Laravel that the output should be a json response, so it will convert it to json. Note that this will only work with the validation rules and when returning objects like return $someObject. To more further customization you shoud use something like:
return response()->json(['data' => $someObject], 200);
$validator = Validator::make($request->all(), [
'password' => [
'required',
'confirmed',
'between:8,55'
]
]);
if ( $validator->fails() ) {
return response()->json( [ 'errors' => $validator->errors() ], 400 );
}
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...