Passport - Create client credentials grant client programmatically - laravel

The documentation Here suggests php artisan passport:client --client for creating a client, but I want to do it using a controller, or ideally, using the native JSON apis provided by passport.
Is that possible at all or will I have to override the methods in Passport::client ?

Old question, but here's a 2021 answer for people finding this on Google.
I find calling Artisan commands from code unsavory, especially as #kishore-kadiyala mentioned because you get back printed output, rather than code.
Instead, you can use the Laravel\Passport\ClientRepository class to create a client directly:
use Laravel\Passport\ClientRepository;
// ... snip ...
$clients = App::make(ClientRepository::class);
// 1st param is the user_id - none for client credentials
// 2nd param is the client name
// 3rd param is the redirect URI - none for client credentials
$client = $clients->create(null, 'My Client Name', '');
Here, $client is a Laravel\Passport\Client instance directly. This is exactly how the Artisan command creates the clients anyway.

You can do
Artisan::call('passport:client --client');
Look at Artisan

Related

Using Laravel Test 7 and Laravel Passport 9.3 with Personal Access Client gives exception "Trying to get property 'id' of non-object"

I am designing a custom authentication scheme (based on public keys) alongside a stateless API, and decided Passport would fulfill the need for post-authentication requests.
Assuming the authentication succeeds, and the user is authenticated, they would receive a Personal Access Token, and use the token for all further requests. The trouble I'm experiencing (still after much searching through various forums and Stack Overflow) is that when using Laravel's built in testing suite, on the createToken() method, it generates an (admittedly common) exception:
"ErrorException : Trying to get property 'id' of non-object".
I am able to manually create a user through Tinker, and create a token through Tinker. However I'm experiencing problems when attempting to automate this process after authenticating.
Here is the relevant code snippet post-authentication:
Auth::login($user);
$user = Auth::user();
$tokenResult = $user->createToken('Personal Access Token');
$token = $tokenResult->token;
$token->expires_at = Carbon::now()->addWeeks(1);
$token->save();
return response()->json([
"access_token" => $tokenResult->accessToken,
"token_type" => "Bearer",
"expires_at" => Carbon::parse(
$tokenResult->token->expires_at)->toDateTimeString()
],
200);
I've manually called Auth::login on the user, to ensure the user is logged in, and Auth::user() returns the user (not null). Upon executing the third line of code, the exception is thrown with the following mini stack-trace (I can provide a full stack-trace if requested).
laravel\passport\src\PersonalAccessTokenFactory.php:100
laravel\passport\src\PersonalAccessTokenFactory.php:71
laravel\passport\src\HasApiTokens.php:67
app\Http\Controllers\Auth\LoginController.php:97
laravel\framework\src\Illuminate\Routing\Controller.php:54
laravel\framework\src\Illuminate\Routing\ControllerDispatcher.php:45
From running this through debug a few times- even though the class is called and loaded, and it appears the Client is found through ControllerDispatcher -> Client::find(id) and found in ClientRepository, when it gets to PersonalAccessTokenFactory, the $client passed in is null (which explains why the $client->id can't be found, though I have no idea why the $client is null at this point).
protected function createRequest($client, $userId, array $scopes)
{
$secret = Passport::$hashesClientSecrets ? Passport::$personalAccessClientSecret : $client->secret;
return (new ServerRequest)->withParsedBody([
'grant_type' => 'personal_access',
'client_id' => $client->id,
...
}
Things I have done/tried with some guidance from the documentation and other posts:
Manually created a user in Tinker, and created the token through Tinker- this does work.
Ensured the user is logged in before attempting to generate token.
passport:install (and adding the --force option)
Ensured Personal Access Client is generated with passport:client --personal
Ensured the AuthServiceProvider::boot() contains the ClientID and Client Secret (in the .env).
migrate:refresh followed by passport:install --force
Complete removal of Passport, removing all files, keys, migrations, and DB entries, followed with a migrate:refresh and reinstallation of Passport, along with generating an additional personal access client (even though one is generated during passport:install).
I'm not sure where else to look/what else to try at this point, so any help or guidance would be much appreciated!
I eventually discovered the solution. The problem is multi-layered, in part having to do with outdated Laravel documentation in regards to testing and Passport Personal Access Clients.
The first part of the problem had to do with using the trait RefreshDatabase on my unit test. Since this creates a mock database with empty datasets, although the clients themselves exist in the real database and the .env file, when the test is run, the test does not see those clients as existing in the mock database. To solve this problem, you must create a client in the setup function before the test is run.
public function setUp() : void
{
parent::setUp();
$this->createClient(); //Private method->Full code below
}
This solves the issue about having a null client during testing, but starting in Laravel 7, Laravel added a requirement for Personal Access Clients that the id and the client secret has to be kept inside the .env file. When running the test, the test will see the actual client id and secret in the .env, and fail to validate these with the client that was created and stored in the mock database, returning another exception: "Client Authentication Failed".
The solution to this problem is to create a .env.testing file in your main project directory, copying your .env file contents to it and ensuring that the keys below exist with values for either your main created Personal Access Client, or copying the secret from a client generated just for testing (I would advise the latter).
PASSPORT_PERSONAL_ACCESS_CLIENT_ID=1
PASSPORT_PERSONAL_ACCESS_CLIENT_SECRET=unhashed-client-secret-value
Then using the code below, make sure the $clientSecret value is the same as the key value in your .env.testing file.
private function createClient() : void
{
$clientRepository = new ClientRepository();
$client = $clientRepository->createPersonalAccessClient(
null, 'Test Personal Access Client', 'http://localhost'
);
DB::table('oauth_personal_access_clients')->insert([
'client_id' => $client->id,
'created_at' => new DateTime,
'updated_at' => new DateTime,
]);
$clientSecret = 'unhashed-client-secret-value';
$client->setSecretAttribute($clientSecret);
$client->save();
}
This will create a new client, set the attribute secret to the value in the variable and update the mock database secret to contain the same value. Hopefully this helps anyone with the same issue.
Another way to prevent copy/paste source code is to just call artisan command in the setup method.
public function setUp() {
parent::setUp();
$this->artisan('passport:install');
}
original here
Just use the facade
public function setUp() {
parent::setUp();
Artisan::call('passport:install');}

Changing The Laravel Passport/OAuth-Server Responses

I'm building an API in Laravel 5.4, using Laravel Passport 3 for authentication. All of my API methods return a set of values that are always returned, success, errors (if there are any errors) etc.
I've changed the response of a \Illuminate\Auth\AuthenticationException throws, to fit with the rest of my app, however I'm not sure how to change the response of various token grant responses, without doing something horrible like editing the vendor files.
I think you can use middleware to change your response.
From laravel documentation:
Before & After Middleware
Whether a middleware runs before or after a
request depends on the middleware itself.
You can capture the response and re-format the response.
You can use laravel's setContent method to set the content in response. Check here.
What you are trying to do here is not supported by the library, so whatever you do will be hacky and will probably break the compatibility with future versions of laravel/passport.
In my opinion, you can only choose between those 2 options:
Instead of declaring passport routes (Passport::routes()) you can declare equivalent routes to your custom methods. Those method internally calls Passport classes and methods, handling passport returning values before returning them to the user. It requires a lot of digging into passport code but, at the same time, if you only add some fields (success or error) you should be able to update your code without too much effort when updating the library.
Fork laravel/passport and modify it to suit you needs. This solution in not as messy as the first, but a merge with new versions of passport in the future will probably be hard.
Of course, both are not great solutions. Keeping the standard passport responses or use a more suitable library are better options: I assume they are not feasible if you are asking.
Another way - create proxy routes for your purposes.
Route::post('custom-auth/token', function (Request $request) {
$proxy = Request::create('oauth/token', 'POST', $request->request->input());
$response = app()->handle($proxy);
return responseCallback($response);
});
Where responseCallback your custom response modificator function.

Laravel Socialite InvalidStateException in AbstractProvider.php line 200

I'm building a web app in my local system (Ubuntu-14.04 64Bit) using laravel 5.3. I used Socialite to signin from social networks. I configured G+, Facebook, GitHug. I'm using Chromium as my default browser. Finally the problem is i'm getting
InvalidStateException in AbstractProvider.php line 200
frequently. i tried
php artisan cache:clear
php artisan config:clear
composer dump-autoload
these are helping to solve the issue temporarily, again the problem raising.
please help me in this issue..
I have the same issue and I've read a lot about this, that depend if the URL where you are at the moment of the login request has www. at the beginning or not.
Into config\services.php, if you have the redirect set as http://sitename.tld/callback/facebook the oauth works if you send the login request from sitename.tld, while if you try from www.sitename.tld you get the exception.
I haven't yet understood how to have it working with and without www at the beginning.
If the AbstractProvider.php line 200 fires the exception when the state of the user is not present means that the User cannot be created.
First check your code when you get the details from the provider(facebook, github) if you create a user and you return it.
If you have managed and logged in your app and you deleted the user from the user table remember to delete also the data from the socialite account table.
I was getting that exception because 'state' wasn't saved in session. But I was using asPopup method - Socialite::driver('facebook')->asPopup()->redirect(); so I saved session then - $request->session()->save();. So I solved this issue.
or try
session()->put('state', $request->input('state'));
$user = Socialite::driver('facebook')->user();
it works
I have same issue and solved in 3 steps;
add request on the top
use Illuminate\Http\Request;
Pass request object to function
public function handleProviderCallback(Request $request)
{
try {
$user = Socialite::driver('facebook')->user();
} catch (Exception $e) {
throw new Exception;
}
}
Clear cache.
php artisan cache:clear
I had the same error but my solution was a little different. I am posting here just in case someone else keeps hitting this post like I did for a possible answer.
I develop on Ubuntu 18.04 desktop since it is a server with a GUI. Socialite works great locally but as soon as I pushed/pulled the changes through git to the server, it quit.
I was running traces by recording what was sent to and from google. I "dd($_GET)" to get a raw dump before Socialite had a chance to get the info so I knew what was stored and ready for use. All info was there but Socialite didn't seem to "see" it. That is when I reasoned it was my apache2 header configuration interfering with the cookies/session data.
I had set header security in my apache2 configs. One of the settings was
Header always edit Set-Cookie ^(.*) "$1;HttpOnly;Secure;SameSite=Strict"
This setting was interfering with the cookie information that socialite needed. I removed that setting from my apache2 header config(by commenting out) and restarted Apache. Finally I removed all sessions in storage/framework/session/* and cleared them from my browser just to be sure. That worked for me.
After I got it working, one by one enabled and tested each of the following settings to have the framework secure what header info it can:
SESSION_SECURE_COOKIE=true
in my .env file
'http_only' => true, and
'same_site' => 'lax'(setting to "strict" did not seem to work)
in my config/session.php file.
Now it is back to testing security and tweaking things back if need be.

Bitbucket - Webhook keep returning error 500

Would like to check, I am fairly new to Bitbucket's new introduced webhook where previously i was using services where Bitbucket will execute a link to my site thus triggering a deployment script.
So since the old service is going to be depreciated soon, we all migrated to webhook instead. With the same implementation, I keep getting an error 500 upon commit/push/merge and there is no way for us to see the details for the error given. At first I thought it was my server giving problem but when i call the link manually via browsers and everything was fine. The deployment script can be executed successfully so then why bitbucket's webhook keeps telling me error 500?
Subsequently I find the guide given by Bitbucket was not helpful. There is no specified call method to the url stated so is the webhook initiates a GET or POST request? previously using services initiates a POST request. Then, are there any necessary payloads i need to include into the webhook URL? None is stated. Then, if there is an error at least let me see the error so I can fix it instead of telling me error 500.
I hope someone here can help me with this issue. Below are some specification of the site.
Server : Ubuntu LEMP 14.04 x64 Laravel framework 5.0
Webhook Url: bot.example.com/bitbucket/deploy/{Site API}
Method : GET
And when the abode link is call, it reaches a controller that does
public function attemptDeploy($site_api)
{
$script = 'nohup setsid php ~/scripts/deploy.php ' . $site_api. ' > /dev/null 2>&1 &';
exec($script);
return response('Deploy running.', 200);
}
Note that when i call this link manually either form browser or console everything works perfectly except from bitbucket's webhook. How can i solve this issue?
I was in the same situation. Bitbucket trims the body of the response and I couldn't see the error given by my server.
I've looked into the logs storage/logs/laravel.log and saw TokenMismatchException. Webhooks being API calls they don't store cookies or sessions so CSRF from Laravel breaks.
You need to add an exception from CSRF for the bitbucket deploy route. You can add this exception in app/Http/Middleware/VerifyCsrfToken.php. For example if your link is www.your_site.com/bit_deploy you will have:
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as BaseVerifier;
class VerifyCsrfToken extends BaseVerifier
{
/**
* The URIs that should be excluded from CSRF verification.
*
* #var array
*/
protected $except = [
'bit_deploy'
];
}
Hope that this helps you ... as I've lost 3 hours on this.
PS: at the time of writing this answer, bitbucket webhooks performs POST calls (not GET)

how to implement the code below in laravel for a password

i am following along a video course from tutsplus -getting started with laravel- and managed up to vid 19 securing the admin panel. My problem as follows. i cannot login to the system because i don't have a valid email-password. in the db there are dummy user emails and hashed pw's but i cannot use because already hashed and i don't know the pw's.
i found this post
How to create a laravel hashed password
however artisan tinker doesn't work because REPL not supported.Falling back to simple shell.
$password = 'JohnDoe';
$hashedPassword = Hash::make($password);
echo $hashedPassword; // $2y$10$jSAr/RwmjhwioDlJErOk9OQEO7huLz9O6Iuf/udyGbHPiTNuB3Iuy
makes sense - to create a new user with known pw but i don't know where and how to implement the code so it spits out the hash in the browser. I tried making a file in the model as well as route and stuck in the code.. to no avail. or maybe easier way?
Can someone help thx.
Place this in your routes.php
Route::get('hash/{password}', function($password){
echo Hash::make($password);
});
And then open your-project.dev/hash/JohnDoe in your browser.

Resources