Lumen Passport (dusterio/lumen-passport) - make login accessible - laravel

I am a novice with Lumen and have recently integrated dusterio/lumen-passport via composer into my project. Following a tutorial I have successfully created authentication for 'client' instances, so I am able to send variables
grant_type: client_credentials
client_id: {my id}
client_secret: {my secret}
to /oauth/token and get a bearer token. That is working great.
What I need to be able to do, and I cannot find sufficient documentation anywhere, is to create user login functionality. This is so that I can hook a UI up to the Lumen API and users be able to enter their email address and password to get access. If any one has any information to help me achieve this I would be extremely grateful. Below are edits I have made to set up the passport process...
bootstrap/app.php
$app->routeMiddleware([
'client.credentials' => Laravel\Passport\Http\Middleware\CheckClientCredentials::class,
]);
$app->register(App\Providers\AuthServiceProvider::class);
$app->register(Laravel\Passport\PassportServiceProvider::class);
$app->register(Dusterio\LumenPassport\PassportServiceProvider::class);
config/auth.php
'defaults' => [
'guard' => env('AUTH_GUARD', 'api'),
'passwords' => 'users'
],
'guards' => [
'api' => [
'driver' => 'passport',
'provider' => 'users'
],
],
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => \App\User::class
]
],
routes/web.php
$router->group(['middleware' => 'client.credentials'], function () use ($router) {
$router->get('/test', 'TestController#index');
});

The way I did it with my laravel based client (seperate apps) was to save the token to a cookie which gets called each request using middleware to authenticate the request heres my code.
<?php
namespace App\Http\Controllers;
use GuzzleHttp\Client;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cookie;
class AuthController extends Controller {
public function __construct()
{
}
public function showLoginPage()
{
return view('Login');
}
public function attemptLogin(Request $request)
{
$client_id = env('CLIENT_ID');
$client_secret = env('CLIENT_SECRET');
$username = $request->input('email');
$password = $request->input('password');
$guzzle = new Client;
$response = $guzzle->post('https://api.domain.com/oauth/token', [
'form_params' => [
'grant_type' => 'password',
'client_id' => $client_id,
'client_secret' => $client_secret,
'username' => $username,
'password' => $password,
'scope' => '*',
],
]);
$reply = json_decode($response->getBody(), true);
$token = $reply['access_token'];
return redirect('/')->cookie('token', $token);
}
public function attemptLogout(Request $request)
{
$accessToken = $request->cookie('token');
$client = new Client(['base_uri' => 'https://api.domain.com']);
$headers = [
'Authorization' => 'Bearer ' . $accessToken,
'Accept' => 'application/json',
];
$response = $client->request('GET', 'logout', [
'headers' => $headers
]);
$status = $response->getStatusCode();
if($status === 200)
{
return redirect('/login')->withCookie(Cookie::forget('token'))->with('success','Logout Successful');
} else {
return response('API Logout Failed', 500);
}
}
}

Related

Testing internal HTTP Request

Hello Laravel Developers,
Today i'm facing some seriously but not at all issue related to testing in Laravel Framework.
See the following code example:
<?php
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\TokenRequest;
use App\Repositories\ExceptionRepository;
use GuzzleHttp\Client;
use Illuminate\Http\JsonResponse;
class TokenController extends Controller {
public function __invoke(TokenRequest $request) : JsonResponse
{
$http = new Client;
try {
$response = $http->post(env('OAUTH_DOMAIN', 'https://oauth.application.com') . '/oauth/token', [
'form_params' => [
'grant_type' => 'password',
'client_id' => env('PASSPORT_PASSWORD_CLIENT_ID'),
'client_secret' => env('PASSPORT_PASSWORD_CLIENT_SECRET'),
'username' => $request->input('email'),
'password' => $request->input('password'),
'scope' => '*',
],
]);
$token = json_decode((string) $response->getBody(), true);
return response()->json([
'message' => 'Token retrieved successfully.',
'token' => $token,
]);
} catch (Throwable $throwable) {
ExceptionRepository::emergency('Something went wrong.', $throwable);
return response()->json([
'message' => 'Token can\' be retrieved.',
'error' => 'Internal server error'
], 500);
}
}
}
This code seems fine but when you're testing you always get internal server error. The TestCase is the following:
<?php
use App\Models\User;
public function test_token()
{
$user = User::factory()->create()->first();
$response = $this->json('POST', '/api/token', [
'email' => $user->email,
'password' => 'password',
]);
$response->assertStatus(500);
}
My ideas about why this happens is because the enviroment variable OAUTH_DOMAIN is different of the right application URL when the code is running via PHPUnit.
In other hand when i try this controller i just need start two instances of artisan serve because when the framework do a internal request it keep frozen and stuck for infinite time. The OAUTH_DOMAIN variable is always the second port of the artisan instance.
Someone know how to face this issue?

Issue with authentication with a laravel spa, it will not always be logged in and ads incorrect cookies

Working on a laravel 8 SPA with Vue and Sanctum. As a newbie with laravel I run into a issue.
I would guess that the issue is with the laravel session cookies as sanctum seems the keep on working but I could be wrong.
How I reproduce the problem:
When I login it works, sanctum token is generated, CXRF works, laravel auth works and when I
refresh the page it still keeps working. Still on the admin page.
When I hit the logout button it will delete all auth cookies and vuex values and I get redirected to the login page. This looks good
I want to login back but at this point it will throw an POST http://localhost:3000/auth/login 419 (unknown status)
3a. To prevent this I refresh the page first, I hit the login button and I am back on the admin page.
When I do the tirst 3 steps there's a chance that the authencation stops working and I will be redirected to the login page when I refresh the page.
What I have seen is that I have 4 cookies present when all is working propper.
appname_session (this the session?)
CXRF_token
io (not sure what this does)
random value
But I have seen is that it will add a 5th(or sometimes even more) cookie with a session value and this will lead to a logout and a invalid session(laravel side)
I have 4 routes, (web & api not in use for now)
If I need to post any files here let me know as I dont know what I need to post here
config/auth.php
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
// 'driver' => 'token',
'provider' => 'users',
'hash' => false,
],
'admin' => [
// 'driver' => 'token',
'provider' => 'users',
],
'auth' => [
'driver' => 'session',
'provider' => 'users',
'hash' => false,
],
],
routes/auth.php
Route::group(['middleware' => 'auth:sanctum'], function () {
Route::post('logout', [LoginController::class, 'logout']);
});
loginController.php
class LoginController extends Controller
{
use AuthenticatesUsers;
public function __construct()
{
$this->middleware('guest')->except('logout');
}
protected function validator(array $data)
{
//validation rules
}
public function login(Request $request)
{
if ($this->validator($request->all())->fails()) {
$errors = $this->validator($request->all())->errors()->getMessages();
$clientErrors = [];
foreach ($errors as $key => $value) {
$clientErrors[$key] = $value[0];
}
return $this->sendError( 'Validation error', $clientErrors, 201);
} else {
$credentials = [
'email' => $request->email,
'password' => $request->password,
];
if (auth()->attempt($credentials)) {
$user = User::where('email', $request->email)->first();
$token = $user->createToken('auth_token')->plainTextToken;
$response = [
// data
];
return $this->sendResponse( 'Login successfully', $response, 200);
} else {
$msg = 'Invalid login';
$clientErrors = [
// errors
];
}
return $this->sendError( $msg, $clientErrors, 201);
}
}
public function logout(Request $request)
{
if (!empty($request->user())) {
AuthController::removeToken($request);
$request->session()->invalidate();
$request->session()->regenerateToken();
// Session::flush();
}
}
}

Laravel Passport - Feature test returns unauthorized 401

I'm trying to test my login endpoint where a successful response would return the access_token among other things.
I'm using RefreshDatabase, so I changed the login method on the controller to retrieve the client_secret via a DB call. I tested with a dd() and I can confirm that the client_secret changes on each phpunit run in the terminal. The credentials are correct and the API endpoint works - just not when it's run via a test. For example, I have the passport tables set up on my mysql server and I can login successfully when running Postman. It's only when trying to run a test do I get a 401 error.
Here is my AuthTest
<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
class AuthTest extends TestCase
{
use RefreshDatabase;
/**
* #test
*/
public function a_user_receives_an_access_token()
{
\Artisan::call('passport:install');
$user = factory('App\User')->create();
$response = $this->json('POST', '/api/login', [
'username' => $user->email,
'password' => 'password'
]);
$response
->assertJson([
'access_token' => true
]);
}
}
routes/api.php
Route::post('login', 'AuthController#login');
AuthController#login:
public function login(Request $request) {
$http = new \GuzzleHttp\Client;
try {
$response = $http->post(config('services.passport.login_endpoint'), [
'form_params' => [
'grant_type' => 'password',
'client_id' => '2', //config('services.passport.client_id')
'client_secret' => DB::table('oauth_clients')->where('id', 2)->pluck('secret')[0], //config('services.passport.client_secret'),
'username' => $request->username,
'password' => $request->password
]
]);
return $response->getBody();
} catch (\GuzzleHttp\Exception\BadResponseException $e) {
if ($e->getCode() == 400 || $e->getCode() == 401) {
return response()
->json([
'status' => $e->getCode(),
'message' => 'Your email and/or password are incorrect',
'expanded' => $e->getMessage()
]);
}
return response()
->json([
'status' => $e->getCode(),
'message' => $e->getMessage()
]);
}
}
I took a look at this question and the accepted answer: How to test authentication via API with Laravel Passport?
I am unable to use the following
public function setUp() {
parent::setUp();
\Artisan::call('migrate',['-vvv' => true]);
\Artisan::call('passport:install',['-vvv' => true]);
\Artisan::call('db:seed',['-vvv' => true]);
}
This results in an error:
PHP Fatal error: Declaration of Tests\Feature\AuthTest::setUp() must be compatible with Illuminate\Foundation\Testing\TestCase::setUp()
Edit: I just added
public function setUp() :void {
parent::setUp();
\Artisan::call('migrate',['-vvv' => true]);
\Artisan::call('passport:install',['-vvv' => true]);
\Artisan::call('db:seed',['-vvv' => true]);
}
but the problem still persists
Edit again:
If I test the oauth route directly, it passes.
public function testOauthLogin() {
$oauth_client_id = 2;
$oauth_client = OAuthClient::findOrFail($oauth_client_id);
$user = factory('App\User')->create();
$body = [
'username' => $user->email,
'password' => 'password',
'client_id' => $oauth_client_id,
'client_secret' => $oauth_client->secret,
'grant_type' => 'password',
'scope' => '*'
];
$this->json('POST','/oauth/token',$body,['Accept' => 'application/json'])
->assertStatus(200)
->assertJsonStructure(['token_type','expires_in','access_token','refresh_token']);
}
But my custom endpoint that uses guzzle fails. I do not know why
Edit again:
I think the issue is with Guzzle, but I'm not sure. I found another implementation of what I'm trying to do, which is the following:
public function login(Request $request) {
$request->request->add([
'username' => $request->username,
'password' => $request->password,
'grant_type' => 'password',
'client_id' => $this->client->id,
'client_secret' => $this->client->secret,
'scope' => '*'
]);
$response = Route::dispatch(Request::create(
'oauth/token',
'POST'
));
}
The above works.

Laravel custom table with API token

I am using a custom table customer for login and also Laravel Passport to generate token for my Ionic mobile app. I am stuck at the login code where I need to generate token after query from my custom table.
Below are the login code... I can successfully retrieve the id from database but how to link it with the token?
public function login(Request $request)
{
$phone = $request->input('phone');
$password = $request->input('password');
$user = $token = array();
try {
$rs_login = DB::select("select a.id from customer a where a.active > 0 and a.phone = ? and a.password = ?", [$phone, $password]);
$numrow_login = count($rs_login);
if ($numrow_login != 1) {
$this->error['form-message'] = 'ERR' . __LINE__ . ': Invalid phone number or password';
} else {
$user['id'] = $rs_login[0]->id;
}
} catch (\Illuminate\Database\QueryException $ex) {
$this->error['form-message'] = 'Login service is unavailable';
}
if ($this->error == '') {
$tokenResult = $user['id']->createToken('Personal Access Token'); // How to pass the $user['id'] to generate token?
$token = $tokenResult->token;
$token->save();
$token['access_token'] = $tokenResult->accessToken;
$token['token_type'] = 'Bearer';
$token['expires_at'] = Carbon::parse(
$tokenResult->token->expires_at
)->toDateTimeString();
}
if ($this->error == '') {
$response['status'] = 'success';
$response['message'] = 'Login successful';
$response['token'] = $token;
} else {
$response['status'] = 'error';
$response['error'] = $this->error;
$response['message'] = (isset($this->error['form-message'])) ? $this->error['form-message'] : 'Please check the form';
}
return response()->json($response);
}
You can just specify a custom table in the config/auth.php instead of rewriting the entire logic
'api' => [
'driver' => 'token',
'provider' => 'customer', // <--- Here
'hash' => false,
],
And create the provider to use the specific table from the database
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\User::class,
],
'customer' => [
'driver' => 'database',
'table' => 'customer',
],
],
And keep the default login behavior for passport
Hope this helps
// changes to be made in this file: config/auth.php
// Create a custom model to override the User model
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\User::class,
],
//custom provider
'customProvider' => [
'driver' => 'eloquent',
'model' => App\CustomModel::class, // put custom model here
],
],
// attach your custom provider within the api guard to use with api calls
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'passport',
'provider' => 'customProvider', // put custom provider here
],
],
// Use lik this in your api router
Route::group(['middleware' => 'auth:api'], function() {});
// I used it like this in LoginController
$user = CustomModel::where('email', $request -> email) -> first();
if(!Hash::check($request -> password, $user -> password)){
return response()->json(['message' => 'Authentication failed. Please check your
credentials and try again!'], 401);
}
Auth::login($user);
$accessToken = Auth::user() -> createToken('authToken') -> accessToken;

Auth ::attempt is not working for login

My auth attempt when user login is not working as expected.It will always return else part when trying to attempt auth
public function tunneluserAuth(Request $request)
{
$this->validate($request, [
'email' => 'required|email',
'password' => 'required',
]);
$password=Hash::make($request->input('password'));
if (Auth::attempt(array('userLogin' => $request->input('email'),
'userPassword' => $password))) {
echo "hi";exit;
}
else {
echo "here";exit;
}
}
and my auth.php page is like,
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Entity\User::class,
],
'companyUsers' => [
'driver' => 'database',
'table' => 'companyUsers',
],
],
But it always returns else condition.why?
Try to use auth credential keys as email and password instead of userLogin and userPassword.
public function tunneluserAuth(Request $request)
{
$this->validate($request, [
'email' => 'required|email',
'password' => 'required',
]);
if (Auth::attempt(['email' => $request->input('email'),
'password' => $request->input('password')])) {
echo "hi";exit;
}
else {
echo "here";exit;
}
}
If you still want to use your own credential keys the use this -
$userData = User::where('userLogin',$request->input('email'))->first();
Auth::loginUsingId($userData->id);
Hope this will work for you.
Try this snippet:
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\Auth;
class LoginController extends Controller
{
/**
* Handle an authentication attempt.
*
* #return Response
*/
public function authenticate()
{
if (Auth::attempt(['email' => $email, 'password' => $password])) {
// Authentication passed...
return redirect()->intended('dashboard');
}
}
}`
Source: Laravel Documentation

Resources