This question already has answers here:
Where API credentials should be stored in laravel?
(2 answers)
Closed 4 months ago.
I am wiring an laravel web application for a small project.
One of the features is to access the API of a foto database (piwigo).
I am very new to laravel and have 0 experience in web security so I was wondering what is the best way to set it up.
I managed to setup the API access trough laravel by creating a PiwigoClient model with a login method, that will pass the response coockies to other request to piwigo:
class PiwigoClient extends Model
{
use HasFactory;
public static $apiURL = 'https://not-actual-piwigo-link/ws.php';
public function login() {
$response = Http::asForm()
->post($this::$apiURL . '?format=json', [
'method' => 'pwg.session.login',
'username' => 'UserName',
'password' => 'PassWord'
]);
return $response;
}
public function test() {
$login = $this->login();
$cookies = $login->cookies();
$response = Http::withOptions(['cookies' => $cookies])
->get($this::$apiURL, [
'format' => 'json',
'method' => 'pwg.tags.getImages',
'tag_name' => 'ImageTag'
]);
return $response->json();
}
}
Now I was wondering, it does not feel safe to leave the 'PassWord' like this in the PiwigoClient model file. What would be the safest and most convenient way to "store" the password?
Standard practices are to store "secret" keys in .env and then reference them in your codebase via a config file.
https://laravel.com/docs/9.x/helpers#method-config
// PiwigoClass
public function login() {
$response = Http::asForm()
->post(config('piwigo.url') . '?format=json', [
'method' => 'pwg.session.login',
'username' => config('piwigo.username'),
'password' => config('piwigo.password')
]);
return $response;
}
// config file piwigo.php
<?php
return [
'password' => env('PIWIGO_PASSWORD', 'PassWord'),
'username' => env('PIWIGO_USERNAME', 'UserName'),
'url' => env('PIWIGO_URL', 'https://my_api_url...')
]
?>
Related
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?
I am trying to make auth based on third party api in laravel. not storing or using data from my local db. Here I am keeping api response data as an array just for visualization . It shows error Argument 1 passed to Illuminate\Auth\SessionGuard::login() must be an instance of Illuminate\Contracts\Auth\Authenticatable, string given, called in vendor\laravel\framework\src\Illuminate\Auth\AuthManager.php . How can I fix that. I also made R&D on it. Thanks in advance
$user = [
'status' => '200',
'token' => 'aWsIpvOEZfv4sfSRUGS2dDeGw7',
'id' => '12454545412',
'user' => [
'name' => 'xyz',
'email' => 'xyz#gmail.com',
'phone' => '12344787',
],
];
$user = json_encode($user);
Auth::login($user);
return redirect( '/home' );
You should create User class as a dummy model if you prefer.
namespace App\Helpers; // depends on you
use Illuminate\Foundation\Auth\User as Authenticatable;
class AuthUser extends Authenticatable
{
protected $guarded = [];
}
And you can use like this:
$user = [
'id' => '12454545412',
'name' => 'xyz',
'email' => 'xyz#gmail.com',
'phone' => 1234564897,
'token' => 'aWsIpvOEZfv4sfSRUGS2dDeGw7' // if you need in your user Object
];
$user = new AuthUser($user);
Auth::login($user);
dd(auth()->user());
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.
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);
}
}
}
I am trying to test laravel app using codeception.
Inside Laravel I am using Sentinel for authentication.
I have a problem to authenticate user inside tests.
Non of the following examples works:
$I->authenticateTestUser($I);
$user = \App\User::where('email','=','test#test.com')->first();
$I->amLoggedAs($user);
$I->canSeeAuthentication();
$credentials = [
'email' => 'test#test.com',
'password' => '****',
];
Sentinel::authenticate($credentials, true);
$I->canSeeAuthentication();
$I->amOnPage('/login');
$I->fillField('email', $email);
$I->fillField('password', $password);
$I->click('Login');
$I->canSeeAuthentication();
How I can authenticate in codeception functional tests?
You just have to use sentinel in the _before method :
private $_user;
private $_credentials;
public function _before(FunctionalTester $I)
{
// we create a user
$this->_credentials = [
'last_name' => 'Test',
'first_name' => 'test',
'email' => 'test#test.fr',
'password' => 'password',
];
$this->_user = \Sentinel::register($this->_credentials, true);
// we log this user
\Sentinel::authenticate($this->_credentials);
}
And you will be able to reuse your user or your credentials in your tests.