I'm doing a login with passport (in API) I'm trying to get the tokens generated by the authentication server. However, I can't add extra parameters to the request which is an instance of FormRequest.
On the other hand, if I change my request to an instance of Request, it works.
So, my question how I can add parameters to my query $loginRequest (which is instance of FormRequest)
$loginRequest->request->add($params);
Here my code:
class AuthController extends Controller
{
use ThrottlesLogins;
public function store(LoginRequest $loginRequest)
{
$loginRequest->validated();
if ($this->hasTooManyLoginAttempts($loginRequest)) {
$this->fireLockoutEvent($loginRequest);
return $this->sendLockoutResponse($loginRequest);
}
if (Auth::attempt($this->credentials($loginRequest))){
$client = $this->getClient($loginRequest->name);
$params = [
'grant_type' => 'password',
'client_id' => $client->id,
'client_secret' => $client->secret,
'username' => $loginRequest->email,
'password' => $loginRequest->password,
'scopes' => 'fd',
];
$loginRequest->request->add($params);
$req = Request::create('oauth/token', 'POST');
$response = Route::dispatch($req)->getContent();
return $response;
}
$this->incrementLoginAttempts($loginRequest);
$this->sendFailedLoginResponse($loginRequest);
}
}
To append properties to an instance of FormRequest you can use the merge() method.
public function store(LoginRequest $loginRequest) {
$params = [
'foo' => 'bar',
];
$loginRequest->merge($params);
}
Related
I am new to laravel and trying to do a simple Authentication project using the laravel/passport package provided by the laravel team and I have no problem with sending the token via a httponly cookie and receiving it from a frontend framework Vuejs. I am able to get the authentication working by storing the token into a localstorage on the browser but all the blogs and documentations I have read suggest not to use localstorage for storing a jwt token and most of them suggest to use httponly cookie because it is not accessible from javascript.
and for my layers of authentication use
a proxyRequest util class
an Authcontroller
I do not use the default User model I have a person model
I have a Cors middleware for allowing requests
proxyRequest class
<?php
namespace App\Utilities;
// use Illuminate\Support\Facades\Http;
class ProxyRequest {
public function grantPasswordToken(string $email, string $password)
{
$params = [
'grant_type' => 'password',
'username' => $email,
'password' => $password,
];
return $this->makePostRequest($params);
}
public function refreshAccessToken()
{
$refreshToken = request()->cookie('refresh_token');
abort_unless($refreshToken, 403, 'Your refresh token is expired.');
$params = [
'grant_type' => 'refresh_token',
'refresh_token' => $refreshToken,
];
return $this->makePostRequest($params);
}
protected function makePostRequest(array $params)
{
$params = array_merge([
'client_id' => config('services.passport.password_client_id'),
'client_secret' => config('services.passport.password_client_secret'),
'scope' => '',
], $params);
$proxy = \Request::create('oauth/token', 'post', $params);
$resp = json_decode(app()->handle($proxy)->getContent());
$this->setHttpOnlyCookie($resp->refresh_token);
return app()->handle($proxy)->getContent();
return $resp;
}
protected function setHttpOnlyCookie(string $refreshToken)
{
cookie()->queue(
'refresh_token',
$refreshToken,
14400, // 10 days
null,
null,
false,
true // httponly
);
}
}
Authcontroller
<?php
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Person;
// use App\Person;
// use App\Person;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Auth;
use Cookie;
use App\Utilities\ProxyRequest;
class AuthController extends Controller
{
protected $proxy;
public function __construct(ProxyRequest $proxy)
{
$this->proxy = $proxy;
}
public function user(Request $request)
{
if (Auth::guard('api')->check()) {
return $request->user('api');
}
return "not auth";
}
public function register(Request $request)
{
// $this->validate(request(), [
// 'name' => 'required',
// 'email' => 'required|email',
// 'password' => 'required',
// ]);
$user = Person::create([
'f_name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
]);
$resp = $this->proxy->grantPasswordToken(
$user->email,
$request->password
);
// return response([
// 'token' => $resp->access_token,
// 'expiresIn' => $resp->expires_in,
// 'message' => 'Your account has been created',
// ], 201);
return response()->json($resp);
}
public function check() {
return "Authenticated";
}
public function refreshToken()
{
$resp = $this->proxy->refreshAccessToken();
return response([
'token' => $resp->access_token,
'expiresIn' => $resp->expires_in,
'message' => 'Token has been refreshed.',
], 200);
}
public function logout(Request $request)
{
$token = $request->user()->token();
$token->delete();
// remove the httponly cookie
cookie()->queue(cookie()->forget('refresh_token'));
return response([
'message' => 'You have been successfully logged out',
], 200);
return $request->all();
}
public function login(Request $request)
{
$user = Person::where('email', $request->email)->first();
abort_unless($user, 404, 'User not found.');
abort_unless(
Hash::check($request->password, $user->password),
403,
'Password or mail address not correct.'
);
$resp = $this->proxy->grantPasswordToken($user->email, $request->password);
return response([
'token' => $resp->access_token,
'expiresIn' => $resp->expires_in,
'message' => 'You have been logged in',
], 200);
}
}
routes
Route::middleware('guest')->group(function () {
Route::post('register', 'Api\AuthController#register')->name('register');
Route::post('login', 'Api\AuthController#login')->name('login');
Route::post('refresh-token', 'Api\AuthController#refreshToken')->name('refreshToken');
});
Route::middleware('auth:api')->group(function () {
Route::post('logout', 'Api\AuthController#logout')->name('logout');
});```
middlewares
protected $middleware = [
\App\Http\Middleware\addCookieToHeader::class,
\App\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
\App\Http\Middleware\TrustProxies::class,
\App\Http\Middleware\Cors::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
];
when I hit the login and register routes I am able to generate a token and could find it in my chrome (Application -> cookies with the name refresh token) but if I try to logout
i just get a response that says {message: "UnAuthenticated"} with a 401 error.
I couldnt find anything regarding this on the internet.
I'm trying to configure ldap authentication on cakephp 3.8 using the new cakephp/authentication plugin, and I'm not sure how to match the authenticated ldap user with a local entity.
My config closely follows the documentation and is available in full here.
in my Application.php the Application class implements both the AuthenticationServiceProviderInterface and the AuthorizationServiceProviderInterface
public function getAuthenticationService(ServerRequestInterface $request,
ResponseInterface $response)
{
$service = new AuthenticationService();
$service->loadIdentifier('Authentication.Password', [...]),
$service->loadIdentifier('Authentication.Ldap', [
'fields' => [
'username' => 'username',
'password' => 'password'
],
'host' => 'ldap.forumsys.com',
'port' => '389',
'bindDN' => function($username) {
return 'uid='.$username.',DC=example,DC=com';
},
'options' => [LDAP_OPT_PROTOCOL_VERSION => 3]
]);
$service->loadAuthenticator('Authentication.Session');
$service->loadAuthenticator('Authentication.Form', [
'fields' => [
'username' => 'email',
'password' => 'password'
],
'loginUrl' => '/users/login'
]);
return $service;
}
In my middleware, I'm trying decorate the identity with authorization stuff. When authenticating using the regular local system the $identity is a App\Model\Entity\User, but when logging in with a ldap user it's a Authentication\Identity
So when I call setAuthorization
'identityDecorator' => function (AuthorizationServiceInterface $authorization,
ArrayAccess $identity) {
$identity->setAuthorization($authorization);
}
it fails with a Call to undefined method Authentication\Identity::setAuthorization() since all I have in $identity is
object(Authentication\Identity) {
'config' => [
'fieldMap' => [
'id' => 'id'
]
],
'data' => object(ArrayObject) {
username => 'einstein'
}
}
How would I match an authenticated ldap user with their local counterpart, and transform from Authentication\Identity to App\Model\Entity\User?
The final goal is to also optionally generate local users from ldap data if they don't exist.
middleware attempt
Application.php
public function middleware($middlewareQueue)
{
...
$middlewareQueue->add($authentication);
$middlewareQueue->add($ldap_matcher);
$middlewareQueue->add($authorization);
return $middlewareQueue;
}
LdapMatcherMiddleware.php
class LdapMatcherMiddleware
{
public function __invoke(ServerRequestInterface $request,
ResponseInterface $response, $next)
{
$identity = $request->getAttribute('identity');
if ($identity !== null) {
$identity = $this->buildIdentity($identity);
$request = $request->withAttribute('identity', $identity);
}
$response = $next($request, $response);
return $response;
}
public function buildIdentity($identity)
{
$Users = TableRegistry::getTableLocator()->get('Users');
$username = $identity->getOriginalData()['username'];
$user = $Users->find()->where(['username' => $username])->first();
if (is_null($identity)) {
$user = $this->createLocalUserFromLdap($identity);
}
return $user;
}
public function createLocalUserFromLdap($identity)
{
$Users = TableRegistry::getTableLocator()->get('Users');
$user = $Users->newEntity([
'username' => $identity->getOriginalData()['username']
]);
$Users->save($user);
return $user;
}
}
How would I match an authenticated ldap user with their local counterpart, and transform from Authentication\Identity to App\Model\Entity\User?
I would add another middleware after the authentication middleware and do that step there.
$users = TableRegistry::getTableLocator()->get('Users');
$entity = $users->newEntity($identity->getOriginalData());
Then do whatever you need to do for authorization with this entity.
The final goal is to also optionally generate local users from ldap data if they don't exist.
Just implement your logic somewhere and get the entity the same way as shown above from the identity.
$users = TableRegistry::getTableLocator()->get('Users');
$entity = $users->newEntity($identity->getOriginalData());
// Check if it exists inside this method and if not just create it
$users->createLocalUserFromLDAP($entity);
I do have trouble in setting password reset facility for custom user table (suppose 'customers' table). It was successful to generate a token for password reset but unable to do the reset because of Laravel considering its default table 'users', not the table I suppose to do reset. Changing default table users to customers in Config\Auth.php generate validation error
Passwords must be at least six characters and match the confirmation.
I just copy/pasting what I did so far
<?php
/*
Code Done by Arun
*/
namespace App\Http\Controllers\API\ResetPassword;
use App\Http\Controllers\API\Controller;
use Illuminate\Http\Request;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Support\Facades\Password;
use App\Model\Customer;
use Laravel\Passport\Client;
use Illuminate\Support\Facades\Auth;
class ResetPasswordController extends Controller
{
use ResetsPasswords;
public function __construct()
{
$this->middleware('api');
}
protected function guard()
{
return Auth::guard('api');
}
public function ResetPassword(Request $request)
{
$v = validator($request->only('email', 'token', 'password','confirm_password'), [
'token' => 'required|string|max:255',
'email' => 'required|string|email|max:255',
'password' => 'required|string',
'confirm_password' => 'required|string|min:6',
]);
if ($v->fails()) {
return response()->json(["error"=>$v->errors()->all()], 400);
}
else
{
$response = $this->broker()->reset(
$this->credentials($request), function ($user, $password) {
$this->resetPassword($user, $password);
});
}
return $response == Password::PASSWORD_RESET
? $this->sendResetResponse($response)
: $this->customResetFailedResponse($request, $response);
// $client = Client::where('password_client', 1)->first();
// $request->request->add([
// 'grant_type' => 'password',
// 'client_id' => $client->id,
// 'client_secret' => $client->secret,
// 'username' => $request->email,
// 'password' => $request->password,
// 'scope' => null,
// ]);
// // Fire off the internal request.
// $proxy = Request::create(
// 'oauth/token',
// 'POST'
// );
// return $response ;
}
}
Any Laravel expert can help me with the problem?
Im finally able to figure this out, im just posting answer may help others come across the same problem.
<?php
namespace App\Http\Controllers\API\Auth;
use App\Http\Controllers\API\Controller;
use Illuminate\Http\Request;
use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Support\Facades\Password;
use App\Model\Customer;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Auth;
use GuzzleHttp\Client;
use DB;
class ResetPasswordController extends Controller
{
use ResetsPasswords;
protected $confirmEmail = false;
public function __construct()
{
}
public function resetPasswordCustomer(Request $request)
{
//Custome password reset mail has done in customer model
$v = validator($request->only('token', 'password','password_confirmation'), [
'token' => 'required|string|max:255',
'password' => 'required|string',
'password_confirmation' => 'required|string|min:6',
]);
if ($v->fails()) {
return response()->json(["error"=>$v->errors()->all()], 400);
}
else
{
$request->merge(['email'=> $this->checkTokenAgainstHashedValue($request->token)]);//Add new property to request..
$response = $this->broker()->reset(
$this->credentials($request), function ($user, $password) {
$this->resetPassword($user, $password);
});
}
return $response == Password::PASSWORD_RESET
? $this->authToken($request)
: $this->customResetFailedResponse($request, $response);
return $response ;
}
protected function checkTokenAgainstHashedValue($token)
{
$resetTable = DB::table('password_reset_customers')->select('token','email')->get();
foreach ($resetTable as $value) {
if (Hash::check($token, $value->token)) {
return $value->email;
}
}
return null;
}
protected function authToken(Request $request)
{
$http = new Client();
$response = $http->post('http://abcd.com/oauth/token', [
'form_params' => [
'grant_type' => 'password',
'client_id' => 2,
'client_secret' =>'qazrxGbDwrwbYXwbEbbkUFNO1zGB3eFYQN3AbG3m',
'username' => Auth::user()->email,
'password' => $request->password,
'scope' => '',
],
]);
return json_decode((string) $response->getBody(), true);
}
public function broker()
{
return Password::broker('customers');
}
}
If I try to log into my app with postman, I get an error on the indicated line:
Trying to get property 'id' of non-object
private $client;
public function __construct()
{
$this->client = Client::find(1);
}
public function login(Request $request)
{
$this->validate($request, [
'username' => 'required',
'password' => 'required'
]);
return $this->issueToken($request, 'password');
}
public function issueToken(Request $request, $grantType, $scope = "")
{
$params = [
'grant_type' => $grantType,
'client_id' => $this->client->id, // this line has error
'client_secret' => $this->client->secret,
'scope' => $scope
];
if($grantType !== 'social'){
$params['username'] = $request->username ?: $request->email;
}
$request->request->add($params);
$proxy = Request::create('oauth/token', 'POST');
return Route::dispatch($proxy);
}
Why am I getting this error message?
Note, on newer versions of PHP, this error will show as:
"Attempt to read property "id" on null
The error is because $this->client is null when find() cannot find the record.
You need to be sure if the record exists or not.
Change:
$this->client = Client::find(1);
To:
$this->client = Client::findOrFail(1);
Documentation:
From Laravel Eloquent docs,
this will throw a 404 error if no record with the specified id is found.
Make sure you have record in database table for User model with id = 1. When you're using User::find(1) Laravel tries to get this record from database, if record is absent this will return null
In your issueToken() method-
$client = Client::find(1);
if($client!=null){
$params = [
'grant_type' => $grantType,
'client_id' => $client->id,
'client_secret' => $client->secret,
'scope' => $scope
];
}else{
$params = [
'grant_type' => $grantType,
'client_id' => null,
'client_secret' => null,
'scope' => $scope
];
}
I had the same problem when I was trying access an id which doesn't exist in my project database. This $user= user::findOrFail($id); solved my problem.
I am trying to setup a SPA that consumes a Laravel API protected with Passport.
I started by creating a new Laravel app specifically for this and I then followed the instructions for setting up passport and set up a password grant client.
I can successfully create a new user, save the user to the database, and log the user in. After that, I try to use the newly created user's information along with the password grant clients id and secret to create an access token. At this point I receive the exception.
I read through the log and I saw where the exception was being thrown. Inside League\OAuth2\Server\Grant\PasswordGrant the validateUser method has the following:
if ($user instanceof UserEntityInterface === false) {
$this->getEmitter()->emit(new RequestEvent(RequestEvent::USER_AUTHENTICATION_FAILED, $request));
throw OAuthServerException::invalidCredentials();
}
Seeing this I implemented the UserEntityInterface on my user model and implemented the getIdentifier method but I still receive the Exception. I'm really not too sure where to go from here, any help would be greatly appreciated. Below is some of my code.
Here is my Registration controller:
class RegisterController extends Controller
{
private $tokenService;
public function __construct(AccessTokenService $tokenService)
{
//$this->middleware('guest');
$this->tokenService = $tokenService;
}
public function register(Request $request)
{
$this->validateWith($this->validator($request->all()));
Log::debug('Validated');
$user = $this->create($request->all());
$this->guard()->login($user);
$this->tokenService->boot(Auth::user());
return response()->json($this->tokenService->getNewAccessToken(), 200);
}
protected function guard()
{
return Auth::guard();
}
protected function validator(array $data)
{
return Validator::make($data, [
'name' => 'required|max:255',
'email' => 'required|email|max:255|unique:users',
'password' => 'required|min:6|confirmed',
'password_confirmation' => 'required'
]);
}
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
]);
}
}
And these are the relevant portions of AccessTokenService:
public function getNewAccessToken() {
$http = new Client();
$client = \Laravel\Passport\Client::where('id', 6)->first();
Log::debug($client->getAttribute('secret'));
Log::debug($this->user->getAttribute('email'));
Log::debug($this->user->getAuthPassword());
$response = $http->post('homestead.app/oauth/token', [
'form_params' => [
'grant_type' => 'password',
'client_id' => 6,
'client_secret' => $client->getAttribute('secret'),
'username' => $this->user->getAttribute('email'),
'password' => $this->user->getAuthPassword(),
'scope' => '*'
]]);
unset($client);
$status = $response->getStatusCode();
$body = $response->getBody();
Log::debug($body->getContents());
Log::debug($status);
switch($status)
{
case 200:case 201:
case 202:
$tokens = array(
"user_id" => $this->user->getAttribute('id'),
"access_token" => $body['access_token'],
"refresh_token" => $body['refresh_token']
);
$output = ["access_token" => $this->storeTokens($tokens), 'status_code' => $status];
break;
default:
$output = ["access_token" => '', 'status_code' => $status];
break;
}
return $output;
}
private function storeTokens(array $tokens) {
UserToken::create([
"user_id" => $tokens['user_id'],
"access_token" => bcrypt($tokens['access_token']),
"refresh_token" => bcrypt($tokens['refresh_token'])
]);
return $tokens['access_token'];
}
So I figured out the issue. When I was requesting the access token I was passing in the user's email and password but I was passing the hashed password when I needed to pass in the unhashed password.
My request for an access token looked like this:
$response = $http->post('homestead.app/oauth/token', [
'form_params' => [
'grant_type' => 'password',
'client_id' => 6,
'client_secret' => $client->getAttribute('secret'),
'username' => $this->user->getAttribute('email'),
'password' => $this->user->getAuthPassword(), //Here is the problem
'scope' => '*'
]]);
By passing the Request to the function using the unhashed password like this solved the problem:
$response = $http->post('homestead.app/oauth/token', [
'form_params' => [
'grant_type' => 'password',
'client_id' => 6,
'client_secret' => $client->getAttribute('secret'),
'username' => $request['email'],
'password' => $request['password'],
'scope' => '*'
]]);
In my case it was magic
when i changed username from email format to simple (alphanumeric only) format it works.
please tell the reason if anyone have for my case.
Thanks