If you do not use a password, the REST request passes. Otherwise, you get an error:
Error:
"name": "Unauthorized",
"message": "Your request was made with invalid credentials.",
"code": 0,
"status": 401,
"type": "yii\\web\\UnauthorizedHttpException"
Access in User model:
public static function findIdentityByAccessToken($username, $password = null)
{
// throw new NotSupportedException('"findIdentityByAccessToken" is not implemented.');
//return static::findOne(['username' => $username]);
$user = static::findOne(['username' => $username]);
if ($user != null and $user->validatePassword($password)) {
return $user;
} else {
return null;
}
}
And validatePassword function:
public function validatePassword($password)
{
$hash = Yii::$app->getSecurity()->generatePasswordHash($password);
return Yii::$app->getSecurity()->validatePassword($password, $this->password_hash);
}
How to authenticate?
REST APIs work usually by authenticating using tokens. There are different types of auth tokens. This example is using basic auth:
Controller
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['authenticator'] = [
'class' => HttpBasicAuth::className(),
];
return $behaviors;
}
You then need to add a authorization header to your request with the value
Basic base64_encode(username:password)
Related
I try to take data from DB and use it in VUE script, but in console I see message
GET http://lara7.test/api/furnitura 401 (Unauthorized)
In Chrome devtools Network tab I see response
furnitura
{"message":"Unauthenticated."}
Here is my code
routes\api.php
Route::middleware('auth:api')->group( function () {
Route::resource('furnitura', 'API\FurnituraController');
});
App\Http\Controllers\API\BaseController
namespace App\Http\Controllers\API;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
abstract class BaseController extends Controller
{
public function sendResponse($result, $message)
{
$response = [
'success' => true,
'data' => $result,
'message' => $message,
];
return response()->json($response, 200);
}
public function sendError($error, $errorMessages = [], $code = 404)
{
$response = [
'success' => false,
'message' => $error,
];
if(!empty($errorMessages)){
$response['data'] = $errorMessages;
}
return response()->json($response, $code);
}
}
App\Http\Controllers\API\FurnituraController
namespace App\Http\Controllers\API;
class FurnituraController extends BaseController
{
public function index()
{
$furnitura = Furnitura::all();
return $this->sendResponse($furnitura->toArray(), 'Furnitura retrieved successfully.');
}
}
resources\js\app.js
window.axios = require('axios');
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
...
...
mounted() {
console.log("Vue ROOT instance mounted");
axios.get('/api/furnitura').then(response => this.furnitura = response.data);
console.log(this.furnitura);
},
Maybe, you do not pass the auth: API middleware. You are getting 401 from middleware.
maybe you use token for user validation or API user validation that token is missing
I have following function which calls the refresh service to get new token for authorization:
private handle401Error(request: HttpRequest<any>, next: HttpHandler) {
if(!this.isRefreshingToken) {
this.isRefreshingToken = true;
return this.authService.refreshToken()
.subscribe((response)=> {
if(response) {
const httpsReq = request.clone({
url: request.url.replace(null, this.generalService.getUserId())
});
return next.handle(this.addTokenToRequest(httpsReq, response.accessToken));
}
return <any>this.authService.logout();
}, err => {
return <any>this.authService.logout();
}, () => {
this.isRefreshingToken = false;
})
} else {
this.isRefreshingToken = false;
return this.authService.currentRefreshToken
.filter(token => token != null)
.take(1)
.map(token => {
return next.handle(this.addTokenToRequest(request, token));
})
}
}
When the response is not undefined and request is returned back it does not call the new request
Ok the thing was that the bearer was quoted like below:
But I have still one issue the request does not invoke the new request, when I refresh the page it gives data with new token, instead like I previously had unauthorized error.
Quick question.
Would it be possible to changes the JSON validation response of laravel?
This is for a custom API that I am building in Laravel.
Validation process
$validation = $this->validate(
$request, [
'user_id' => 'required',
]);
The response shows up like this in json
{
"message": "The given data was invalid.",
"errors": {
"user_id": [
"The user id field is required."
],
}
}
Preferable it would become something like this.
{
"common:" [
"status": "invalid",
"message": "Param xxxx is required",
],
}
What would be the best way to changes this?
Is it even possible?
Thank you.
You can do this, and it will be reflected globally.
Navigate to below folder and use Controller.php
app/Http/Controllers
use Illuminate\Http\Request;
Write below method in Controller.php and change response as you want.
public function validate(
Request $request,
array $rules,
array $messages = [],
array $customAttributes = [])
{
$validator = $this->getValidationFactory()
->make(
$request->all(),
$rules, $messages,
$customAttributes
);
if ($validator->fails()) {
$errors = (new \Illuminate\Validation\ValidationException($validator))->errors();
throw new \Illuminate\Http\Exceptions\HttpResponseException(response()->json(
[
'status' => false,
'message' => "Some fields are missing!",
'error_code' => 1,
'errors' => $errors
], \Illuminate\Http\JsonResponse::HTTP_UNPROCESSABLE_ENTITY));
}
}
I have tried it with Laravel 5.6, maybe this is useful for you.
#Dev Ramesh solution is still perfectly valid for placing inline within your controller.
For those of you looking to abstract this logic out into a FormRequest, FormRequest has a handy override method called failedValidation. When this is hit, you can throw your own response exception, like so...
/**
* When we fail validation, override our default error.
*
* #param ValidatorContract $validator
*/
protected function failedValidation(\Illuminate\Contracts\Validation\Validator $validator)
{
$errors = $this->validator->errors();
throw new \Illuminate\Http\Exceptions\HttpResponseException(
response()->json([
'errors' => $errors,
'message' => 'The given data was invalid.',
'testing' => 'Whatever custom data you want here...',
], 422)
);
}
I was searching for an answer to this and I think I found a better way. There is an exception handler in a default Laravel app - \App\Exceptions\Handler - and you can override the invalidJson method:
<?php
namespace App\Exceptions;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Validation\ValidationException;
class Handler extends ExceptionHandler
{
// ...
protected function invalidJson($request, ValidationException $exception)
{
$errors = [];
foreach ($exception->errors() as $field => $messages) {
foreach ($messages as $message) {
$errors[] = [
'code' => $field,
'message' => $message,
];
}
}
return response()->json([
'error' => $errors,
], $exception->status);
}
}
Question
Currently looking for how other people handled the validate password function when they need to authenticate with the userprincipalname instead of the Edvlerblog\Adldap2 validatePassword function which uses samaccountname.
Please provide feedback in the comments if you are struggling with
anything specific so we can update the documentation.
Current Implementation
For app/common/model/LoginForm
getUser
The Edvlerblog\Adldap2 getUser() function works, and even caches the queryLdapUserObject, allowing you to fetch any of the AD attributes.
protected function getUser()
{
if ($this->_user === null) {
$this->_user = \Edvlerblog\Adldap2\model\UserDbLdap::findByUsername($this->username);
}
return $this->_user;
}
validatePassword()
Currently, the following validatePassword function does not work for me because in my instance AD must authenticate against the userprincipalname instead of the samaccount name.
public function validatePassword($attribute, $params)
{
if (!$this->hasErrors()) {
$user = $this->getUser();
if (!$user || !$user->validatePassword($this->password)) {
$this->addError($attribute, 'Incorrect username or password.');
}
}
}
A solution
Here is one workaround thanks to the Edvlerblog\Adldap2 who recently released 3.0.5 addressing a couple issues and providing some examples in his readme docs.
Please note the addition of findByAttribute(), allowing the following:
$this->_user = \Edvlerblog\Adldap2\model\UserDbLdap::findByUsername($this->username);
validatePassword() w/ userprincipalname
Update your login model: common\models\LoginForm.php
public function validatePassword($attribute, $params)
{
if (!$this->hasErrors()) {
$user = $this->getUser();
if (!$user) {
$this->addError('username', 'Incorrect username.');
} else {
// Note: queryLdapUserObject is a cached object,
// so the ldap fetch does not get called :-).
$userprincipalname = $this->_user->queryLdapUserObject()->getAttribute('userprincipalname');
$auth = Yii::$app->ad->auth()->attempt($userprincipalname[0], $this->password);
if (!$auth) {
$this->addError('password', 'Incorrect password.');
}
}
}
}
getUser() w/userprincipalname
/**
* Finds user by [[username]]
*
* #return User|null
*/
protected function getUser()
{
if ($this->_user === null) {
$this->_user = \Edvlerblog\Adldap2\model\UserDbLdap::findByUsername($this->username);
}
return $this->_user;
}
Yii2 ldap Component Configuration
Reference: https://github.com/Adldap2/Adldap2/blob/master/docs/configuration.md
Config in your frontend\config\main:
'components' => [
'log' => [... ],
'authManager' => [... ],
'ad' => [
'class' => 'Edvlerblog\Adldap2\Adldap2Wrapper',
'providers' => [
'default' => [
'autoconnect' => true,
'config' => [
'domain_controllers' => ['your.ldap.domain.com'],
'base_dn' => "OU=XXX,OU=XXX,DC=ccccccc,DC=xxxx,DC=com",
'admin_username' => "your_username",
'admin_password' => "your_password",
'port' => 389,
],
],
],
],
],
Im implementing REST API Authentication module as following step
1. Create user by Admin
2. First tim: login by Basic Auth to return access_token
3. Use access_token at step 2 to Auth user by. QueryParamAuth
as this instruction it work with QueryParamAuth
https://github.com/yiisoft/yii2/blob/master/docs/guide/rest-authentication.md
But it not work at step2. Auth by BasicAuth
I debug it. $this->auth always return null. Although $username and $password right
class HttpBasicAuth extends AuthMethod
/**
* #var callable a PHP callable that will authenticate the user with the HTTP basic auth information.
* The callable receives a username and a password as its parameters. It should return an identity object
* that matches the username and password. Null should be returned if there is no such identity.
*
* The following code is a typical implementation of this callable:
*
* ```php
* function ($username, $password) {
* return \app\models\User::findOne([
* 'username' => $username,
* 'password' => $password,
* ]);
* }
* ```
*
* 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;
public function authenticate($user, $request, $response)
{
$username = $request->getAuthUser();
$password = $request->getAuthPassword();
if ($this->auth) {
if ($username !== null || $password !== null) {
$identity = call_user_func($this->auth, $username, $password);
var_dump($identity);
die();
if ($identity !== null) {
$user->switchIdentity($identity);
} else {
$this->handleFailure($response);
}
return $identity;
}
} elseif ($username !== null) {
$identity = $user->loginByAccessToken($username, get_class($this));
if ($identity === null) {
$this->handleFailure($response);
}
return $identity;
}
return null;
}
My question is how can i implement $this->auth function?
HTTP Basic Auth
// controller code
Way 1: user Auth using auth-token
use yii\filters\auth\HttpBasicAuth;
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['authenticator'] = [
'class' => HttpBasicAuth::className(),
];
return $behaviors;
}
Above code will validate user by access token (as mentioned in the doc)
when window prompts to enter username & password
username: hErEaccE55T0ken
password:
Way 2:
To implement custom auth using username & password, sample code (chris code works)
i m using user_email, user_password
public $user_password;
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['authenticator'] = [
'class' => HttpBasicAuth::className(),
'auth' => [$this, 'auth']
];
return $behaviors;
}
/**
* Finds user by user_email and user_password
*
* #param string $username
* #param string $password
* #return static|null
*/
public function Auth($username, $password) {
// username, password are mandatory fields
if(empty($username) || empty($password))
return null;
// get user using requested email
$user = \app\models\User::findOne([
'user_email' => $username,
]);
// if no record matching the requested user
if(empty($user))
return null;
// hashed password from user record
$this->user_password = $user->user_password;
// validate password
$isPass = \app\models\User::validatePassword($password);
// if password validation fails
if(!$isPass)
return null;
// if user validates (both user_email, user_password are valid)
return $user;
}
I implement HttpBasicAuth->auth in my controller where I attach HttpBasicAuth as a behavior like so:
class MyController extends Controller
{
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['authenticator'] = [
'class' => HttpBasicAuth::className(),
'auth' => [$this, 'auth']
]
return $behaviors;
}
public function auth($username, $password)
{
// Do whatever authentication on the username and password you want.
// Create and return identity or return null on failure
}
// ... Action code ...
}