Skip to content

Commit

Permalink
[13.x] Make Passport headless (Support Laravel Jetstream and Breeze) (#…
Browse files Browse the repository at this point in the history
…1771)

* add stubs

* fix styling

* formatting

* add support for Jetstream

* formatting

* formatting

* wip

* add client revoke

* wip

* fix view response

* move stubs to related repos

* wip

* fix URI rule

* wip

* wip

* formatting

* formatting

* add upgrade guide

* move action classes to Jetstream

* formatting

* wip

* revert prev commit

* fix first party check

* fix tests
  • Loading branch information
hafezdivandari authored Sep 18, 2024
1 parent 195058c commit 6142f9c
Show file tree
Hide file tree
Showing 10 changed files with 92 additions and 152 deletions.
13 changes: 13 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,19 @@ PR: https://github.com/laravel/passport/pull/1734

The `league/oauth2-server` Composer package which is utilized internally by Passport has been updated to 9.0, which adds additional types to method signatures. To ensure your application is compatible, you should review this package's complete [changelog](https://github.com/thephpleague/oauth2-server/blob/master/CHANGELOG.md#900---released-2024-05-13).

### Headless

PR: https://github.com/laravel/passport/pull/1771

Passport's views were not rendering properly for several release cycles. Passport is now a headless OAuth2 library. If you would like a frontend implementation of Laravel Passport's OAuth features that are already completed for you, you should use an [application starter kit](https://laravel.com/docs/11.x/starter-kits).

All the authorization view's rendering logic may be customized using the appropriate methods available via the `Laravel\Passport\Passport` class. Typically, you should call these methods within the `boot` method of your application's `App\Providers\AppServiceProvider` class. Passport will take care of defining the routes that return these views:

public function boot(): void
{
Passport::authorizationView('auth.oauth.authorize');
}

### Identify Clients by UUIDs

PR: https://github.com/laravel/passport/pull/1764
Expand Down
93 changes: 0 additions & 93 deletions resources/views/authorize.blade.php

This file was deleted.

6 changes: 2 additions & 4 deletions src/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -153,12 +153,10 @@ protected function redirectUris(): Attribute

/**
* Determine if the client is a "first party" client.
*
* @return bool
*/
public function firstParty()
public function firstParty(): bool
{
return $this->hasGrantType('personal_access') || $this->hasGrantType('password');
return empty($this->user_id);
}

/**
Expand Down
5 changes: 2 additions & 3 deletions src/Contracts/AuthorizationViewResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ interface AuthorizationViewResponse extends Responsable
/**
* Specify the parameters that should be passed to the view.
*
* @param array $parameters
* @return $this
* @param array<string, mixed> $parameters
*/
public function withParameters($parameters = []);
public function withParameters(array $parameters = []): static;
}
10 changes: 10 additions & 0 deletions src/HasApiTokens.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,16 @@ public function token()
return $this->accessToken;
}

/**
* Get the access token currently associated with the user.
*
* @return \Laravel\Passport\AccessToken|\Laravel\Passport\TransientToken|null
*/
public function currentAccessToken()
{
return $this->token();
}

/**
* Determine if the current API token has a given scope.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,32 @@

namespace Laravel\Passport\Http\Responses;

use Closure;
use Illuminate\Contracts\Support\Responsable;
use Laravel\Passport\Contracts\AuthorizationViewResponse as AuthorizationViewResponseContract;
use Laravel\Passport\Contracts\AuthorizationViewResponse;

class AuthorizationViewResponse implements AuthorizationViewResponseContract
class SimpleViewResponse implements AuthorizationViewResponse
{
/**
* The name of the view or the callable used to generate the view.
*
* @var string
*/
protected $view;

/**
* An array of arguments that may be passed to the view response and used in the view.
*
* @var string
* @var array<string, mixed>
*/
protected $parameters;
protected array $parameters = [];

/**
* Create a new response instance.
*
* @param callable|string $view
* @return void
*/
public function __construct($view)
public function __construct(protected Closure|string $view)
{
$this->view = $view;
}

/**
* Add parameters to response.
*
* @param array $parameters
* @return $this
* @param array<string, mixed> $parameters
*/
public function withParameters($parameters = [])
public function withParameters(array $parameters = []): static
{
$this->parameters = $parameters;

Expand Down
58 changes: 49 additions & 9 deletions src/Passport.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
namespace Laravel\Passport;

use Carbon\Carbon;
use Closure;
use DateInterval;
use DateTimeInterface;
use Illuminate\Contracts\Encryption\Encrypter;
use Laravel\Passport\Contracts\AuthorizationViewResponse as AuthorizationViewResponseContract;
use Laravel\Passport\Http\Responses\AuthorizationViewResponse;
use Laravel\Passport\Contracts\AuthorizationViewResponse;
use Laravel\Passport\Http\Responses\SimpleViewResponse;
use League\OAuth2\Server\ResourceServer;
use Mockery;
use Psr\Http\Message\ServerRequestInterface;
Expand Down Expand Up @@ -204,6 +205,8 @@ public static function enablePasswordGrant()
/**
* Set the default scope(s). Multiple scopes may be an array or specified delimited by spaces.
*
* @deprecated Use defaultScopes.
*
* @param array|string $scope
* @return void
*/
Expand All @@ -212,6 +215,32 @@ public static function setDefaultScope($scope)
static::$defaultScope = is_array($scope) ? implode(' ', $scope) : $scope;
}

/**
* Set or get the default scopes.
*
* @param string[]|string|null $scopes
* @return string[]
*/
public static function defaultScopes(array|string|null $scopes = null): array
{
if (! is_null($scopes)) {
static::$defaultScope = is_array($scopes) ? implode(' ', $scopes) : $scopes;
}

return static::$defaultScope ? explode(' ', static::$defaultScope) : [];
}

/**
* Return the scopes in the given list that are actually defined scopes for the application.
*
* @param string[] $scopes
* @return string[]
*/
public static function validScopes(array $scopes): array
{
return array_values(array_unique(array_intersect($scopes, array_keys(static::$scopes))));
}

/**
* Get all of the defined scope IDs.
*
Expand Down Expand Up @@ -599,17 +628,28 @@ public static function tokenEncryptionKey(Encrypter $encrypter)
$encrypter->getKey();
}

/**
* Register the views for Passport using conventional names under the given namespace.
*/
public static function viewNamespace(string $namespace): void
{
static::viewPrefix($namespace.'::');
}

/**
* Register the views for Passport using conventional names under the given prefix.
*/
public static function viewPrefix(string $prefix): void
{
static::authorizationView($prefix.'authorize');
}

/**
* Specify which view should be used as the authorization view.
*
* @param callable|string $view
* @return void
*/
public static function authorizationView($view)
public static function authorizationView(Closure|string $view): void
{
app()->singleton(AuthorizationViewResponseContract::class, function ($app) use ($view) {
return new AuthorizationViewResponse($view);
});
app()->singleton(AuthorizationViewResponse::class, fn () => new SimpleViewResponse($view));
}

/**
Expand Down
17 changes: 0 additions & 17 deletions src/PassportServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ class PassportServiceProvider extends ServiceProvider
public function boot()
{
$this->registerRoutes();
$this->registerResources();
$this->registerPublishing();
$this->registerCommands();

Expand All @@ -63,16 +62,6 @@ protected function registerRoutes()
}
}

/**
* Register the Passport resources.
*
* @return void
*/
protected function registerResources()
{
$this->loadViewsFrom(__DIR__.'/../resources/views', 'passport');
}

/**
* Register the package's publishable resources.
*
Expand All @@ -89,10 +78,6 @@ protected function registerPublishing()
__DIR__.'/../database/migrations' => database_path('migrations'),
], 'passport-migrations');

$this->publishes([
__DIR__.'/../resources/views' => base_path('resources/views/vendor/passport'),
], 'passport-views');

$this->publishes([
__DIR__.'/../config/passport.php' => config_path('passport.php'),
], 'passport-config');
Expand Down Expand Up @@ -134,8 +119,6 @@ public function register()
$this->registerJWTParser();
$this->registerResourceServer();
$this->registerGuard();

Passport::authorizationView('passport::authorize');
}

/**
Expand Down
14 changes: 7 additions & 7 deletions tests/Unit/AuthorizationControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
use Laravel\Passport\Bridge\Scope;
use Laravel\Passport\Client;
use Laravel\Passport\ClientRepository;
use Laravel\Passport\Contracts\AuthorizationViewResponse;
use Laravel\Passport\Exceptions\AuthenticationException;
use Laravel\Passport\Exceptions\OAuthServerException;
use Laravel\Passport\Http\Controllers\AuthorizationController;
use Laravel\Passport\Http\Responses\AuthorizationViewResponse;
use Laravel\Passport\Passport;
use League\OAuth2\Server\AuthorizationServer;
use League\OAuth2\Server\Exception\OAuthServerException as LeagueException;
Expand Down Expand Up @@ -63,16 +63,16 @@ public function test_authorization_view_is_presented()

$user->shouldReceive('getAuthIdentifier')->andReturn(1);

$response->shouldReceive('withParameters')->once()->andReturnUsing(function ($data) use ($client, $user, $request) {
$response->shouldReceive('withParameters')->once()->andReturnUsing(function ($data) use ($client, $user, $request, $response) {
$this->assertEquals($client, $data['client']);
$this->assertEquals($user, $data['user']);
$this->assertEquals($request, $data['request']);
$this->assertSame('description', $data['scopes'][0]->description);

return 'view';
return $response;
});

$this->assertSame('view', $controller->authorize(
$this->assertSame($response, $controller->authorize(
m::mock(ServerRequestInterface::class), $request, $clients
));
}
Expand Down Expand Up @@ -221,16 +221,16 @@ public function test_authorization_view_is_presented_if_request_has_prompt_equal
$clients->shouldReceive('find')->with(1)->andReturn($client = m::mock(Client::class));
$client->shouldReceive('skipsAuthorization')->andReturn(false);

$response->shouldReceive('withParameters')->once()->andReturnUsing(function ($data) use ($client, $user, $request) {
$response->shouldReceive('withParameters')->once()->andReturnUsing(function ($data) use ($client, $user, $request, $response) {
$this->assertEquals($client, $data['client']);
$this->assertEquals($user, $data['user']);
$this->assertEquals($request, $data['request']);
$this->assertSame('description', $data['scopes'][0]->description);

return 'view';
return $response;
});

$this->assertSame('view', $controller->authorize(
$this->assertSame($response, $controller->authorize(
m::mock(ServerRequestInterface::class), $request, $clients
));
}
Expand Down
Loading

0 comments on commit 6142f9c

Please sign in to comment.