Skip to content

[13.x] Release Passport 13.x #1797

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 59 commits into from
Apr 25, 2025
Merged

Conversation

hafezdivandari
Copy link
Contributor

@hafezdivandari hafezdivandari commented Oct 31, 2024

Before Release

After Release

This PR does final touches and improvements that makes Passport ready for release:

  • Add tests for PHP 8.4 Add PHP 8.4 support thephpleague/oauth2-server#1454
  • Add support for Laravel 12.x.
  • Fix many types and PHPDoc types:
    • Add new OAuthenticatable interface. Implemented by HasApiTokens trait.
    • Add new ScopeAuthorizable interface. Implemented by AccessToken and TransientToken classes.
  • Fix the relation between the HasApiTokens and the Client models:
    • This is a backward compatible change. Old projects have the user_id column on the oauth_clients table and can continue using HasApiTokens::clients() and Client::user() relation methods.
    • New projects have owner_id and owner_type columns on the oauth_clients table and will use new HasApiTokens::oauthApps() and Client::owner() relation methods.
    • Why?
      • The HasApiTokens trait can be used on any Authenticatable model, but the old relation method were guessing the related models based on the user provider which is wrong. For example, the owner of the client can be an admin but the client's provider is a regular user. New relations are morphable that fixes this bug.
      • The clients relation method on the HasApiTokens trait that typically will be used on User model, has a very general name! It's very likely that developers use this relation name on their User model, which causes conflict. The new oauthApps and owner names are much clearer for this relationship: The owner has many registered OAuth apps and each OAuth app has an owner.
    • Deprecate HasApiTokens::clients() method, in favor of the new oauthApps() relation method.
    • Deprecate Client::user() method, in favor of the new owner() relation method.
  • Enhance commands' styles by using components.
  • Use Date facade instead of Carbon.
  • Use HasUuid trait on the Client model.
  • Enable PSR-17 auto-discovery:
  • Add support for legacy "client_credentials" tokens on EnsureClientIsResourceOwner middleware, where oauth_user_id was null prior to version 9.0 of league/oauth2-server.
  • Add $user->tokenCant() method to determine a missing scope. (Sanctum has the same method).
  • Improve issuing PATs:
    • Access to token's attributes on result, including token ID and expire time.
    • Improve performance by setting the token's name on grant instead of re-parsing the JWT on the factory.
    • Dispatch an event after access token is issued, just like other grant types.
    • Remove lcobucci/jwt package. Not needed anymore.
  • Use registered exp instead of non-standard expiry claim on JWT cookie.
    • Fix a bug where checking expiry time was mistakenly ignored when Passport::$ignoreCsrfToken was set to true.
  • Pass guard name to AuthenticationException when thrown on AuthorizationController.
  • Better naming for 2 recently added methods before release:
    • Newly added User::getProvider() method has been renamed to getProviderName().
    • Newly added ResolvesInheritedScopes::scopeExists() method has been renamed to scopeExistsIn().
  • Add many more tests / assertions.
  • Improve tests by fixing deprecations, risky, warnings and notices.
  • Cleanup.

Copy link

Thanks for submitting a PR!

Note that draft PR's are not reviewed. If you would like a review, please mark your pull request as ready for review in the GitHub user interface.

Pull requests that are abandoned in draft may be closed due to inactivity.

@jonerickson
Copy link
Contributor

jonerickson commented Nov 16, 2024

@hafezdivandari With the switching of Passport to a headless approach, have you taken into account Inertia external redirects yet? Specifically in ApproveAuthorizationController if using an Inertia component to render the approve screen. Currently the authorization server will return a standard 302 when approving/denying an auth request - Inertia expects a 409 status to perform the correct external redirect. Might need to create and bind a new ApproveAuthorizationResponse to return the correct Inertia::location response. I have tested this in the standard auth code grant flow, I'm sure it happens for implicit as well.

@hafezdivandari
Copy link
Contributor Author

hafezdivandari commented Nov 17, 2024

@jonerickson currently external redirect happens when utilizing "implicit" and "auth code" grants on different places / scenarios:

  • On AuthorizationController:
    • In case of invalide_scope, login_required, and consent_required exceptions.
    • Implicit approval (consent skipped).
  • On ApproveAuthorizationController: explicit approval.
  • On DenyAuthorizationController: access_denied exception.

So it's not easily possible to just bind a response for each case to handle Inertia redirect. But 2 solutions come to mind:

  1. Having a config method (Passport::$responseHandler) to be used on Passport\Http\Controllers\ConvertsPsrResponses (I don't like this ATM).
  2. Having a middleware on the app itself (not Passport) to catch 302 responses and use Inertial::location() method (like this)?

@jonerickson
Copy link
Contributor

@hafezdivandari Right - I understand the various locations of all external redirects - was just giving you an exact example of where this can happen.

I am not a fan of the $responseHandler approach either. I have see other places where support for Inertia is baked right into the source rather than having the app developer responsible for the implementation. Taking that into account, and using how I am currently handling the edge case, is to define a middleware on the routes that can return redirects and handle the status code changes. For example:

$response = parent::handle($request, $next);

if ($response->isRedirection() && $request->inertia() && $response->headers->has('location')) {
    return Response::make('', 409, [Header::LOCATION => $response->headers->get('location')]);
}

return $response;

This removes the responsibility from the app developer into the framework, hopefully reducing the potential for implementation errors. Whatcha think?

@hafezdivandari
Copy link
Contributor Author

@jonerickson As you know, Passport is headless and not dependent on Inertia, so it is not the right place to handle this edge case. You may refer to laravel/jetstream#1521 and laravel/breeze#398 for more.

@hafezdivandari hafezdivandari marked this pull request as ready for review March 7, 2025 17:59
@ercsctt
Copy link

ercsctt commented Mar 13, 2025

@hafezdivandari I have to say, this is amazing work man 🫡

@hafezdivandari
Copy link
Contributor Author

Hi @taylorotwell just a reminder that this PR is ready for review whenever you have time. Thanks.

cc @crynobone

@taylorotwell taylorotwell merged commit 1d89db3 into laravel:13.x Apr 25, 2025
8 checks passed
@hafezdivandari hafezdivandari deleted the 13.x-pre-release branch April 25, 2025 16:56
@Tofandel
Copy link
Contributor

Tofandel commented May 27, 2025

There is nothing in the upgrade guide about the table structure change

v12.4.2...v13.0.0#diff-f04f4d4afe9cb4e808417738c29eb3a975e7b8afbae2720ebb6f9eae5a0c530fL15-L24

v12.4.2...v13.0.0#diff-1f43488d747e018afbadd0065346fa6b3da68a5efb3a4471f1df1e5dbbad8712R1

From what I understand this means the previous table structure is incompatible and a migration is needed

I have made the following

    public function up()
    {
        if (! Schema::hasColumn('oauth_clients', 'owner_type')) {
            Schema::dropIfExists('oauth_personal_access_clients');
            DB::table('oauth_clients')->update(['provider' => 'admins']); // Needs a default provider now

            Schema::table('oauth_clients', function (Blueprint $table) {
                $table->text('grant_types')->default('[]')->after('provider');
            });
            DB::table('oauth_clients')
                ->where('personal_access_client', 1)
                ->update(['grant_types' => ['personal_access']]);
            DB::table('oauth_clients')
                ->where('password_client', 1)
                ->update(['grant_types' => ['password', 'refresh_token']]);
            DB::table('oauth_clients')
                ->where('password_client', 0)
                ->where('personal_access_client', 0)
                ->update(['grant_types' => ['client_credentials']]);

            // This could be optimized to run all the updates in the eachById
            DB::table('oauth_clients')->eachById(function ($client) {
                DB::table('oauth_clients')->where('id', $client->id)->update(['secret' => Hash::make($client->secret)]);
            });
            
            Schema::table('oauth_clients', function (Blueprint $table) {
                $table->renameColumn('user_id', 'owner_id');
                $table->string('owner_type')->after('owner_id')->nullable();
                $table->dropForeign(['user_id']);
                $table->index(['owner_id', 'owner_type']);
                $table->dropColumn('redirect');
                $table->text('redirect_uris')->default('[]'); // Without the default, a php error will be thrown on all previous clients about array expected
                $table->dropColumn('personal_access_client');
                $table->dropColumn('password_client');
            });
        }
    }

And also added the oauth_device_code migration

Am I doing the migration correctly?

If so this should be added to the migration guide

@Tofandel
Copy link
Contributor

image

The error I'm mentionning in the migration

@hafezdivandari
Copy link
Contributor Author

hafezdivandari commented May 27, 2025

From what I understand this means the previous table structure is incompatible and a migration is needed

@Tofandel All changes on the schema are backward compatible, so you don't have to change anything, that's why there is no upgarde entry for that.

#1744 (comment)

@Tofandel
Copy link
Contributor

I see, I still ran into some issues upgrading though. Mainly the secret is now hashed, but before the default was that it wasn't so a migration is needed to hash all the secrets

There is also one file the oauth-public.key in storage, that used to be created with 644, but now the app requires that it has 600, and completely errors out if it doesn't have the 600 permissions

@hafezdivandari
Copy link
Contributor Author

I see, I still ran into some issues upgrading though. Mainly the secret is now hashed, but before the default was that it wasn't so a migration is needed to hash all the secrets

@Tofandel Already explained on upgrade guide: https://github.com/laravel/passport/blob/13.x/UPGRADE.md#client-secrets-hashed-by-default

You may run php artisan passport:hash command to hash all the clients' secrets (Caution: please be aware that running this command cannot be undone. For extra precaution, you may wish to create a backup of your database before running the command.)

There is also one file the oauth-public.key in storage, that used to be created with 644, but now the app requires that it has 600, and completely errors out if it doesn't have the 600 permissions

This is a security enhancement, the check is being done by league/oauth2-server on this line: https://github.com/thephpleague/oauth2-server/blob/2c076b11879d317172391570f03a432736abb20c/src/CryptKey.php#L78

You may:

  • Change the key files permissions (recommended):
    chmod(Passport::keyPath('oauth-public.key'), 0660);
    chmod(Passport::keyPath('oauth-private.key'), 0600);
    
  • Or disable this feature from the boot method of your application's App\Providers\AppServiceProvider class::
    Passport::$validateKeyPermissions = false;
    

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants