Skip to content
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

[13.x] Make client RFC compatible #1744

Draft
wants to merge 7 commits into
base: 13.x
Choose a base branch
from

Conversation

hafezdivandari
Copy link
Contributor

@hafezdivandari hafezdivandari commented May 18, 2024

This PR refactors OAuth2 Client implementation to make it more RFC compatible and removes some redundant codes from Passport.

Client ID

Clients are now always identified by UUID. According to RFC6749 the client identifier must be a unique string. It's also more secure than incremental integer IDs.

Changes

  • id column of the oauth_clients table has been changed to uuid type.
  • client_id column of the oauth_auth_codes and oauth_access_tokens tables have been changed to foreignUuid type.
  • Passport::$clientUuids property has been removed.
  • Passport::clientUuids() method has been removed.
  • Passport::setClientUuids() method has been removed.
  • 'passport.client_uuids' config property has been removed.

Client Secret

Clients' secret are now always hashed.

Changes

  • Hash facade has been used to hash and check the values.
  • Passport::$hashesClientSecrets property has been removed.
  • Passport::hashClientSecrets() method has been removed.

Client Redirect URIs

The redirect property of the Client model has been renamed to redirect_uris and is going to be stored as an array of strings instead of a comma-separated list of values, RFC7591

Changes

  • redirect column of oauth_clients table has been renamed to redirect_uris
  • redirect_uris has been casted to array on Client model.

Client Grant Types

The grant_types property was added to the Client model long time ago (first appearance was on #729 then #731). This PR adds grant_types column to the oauth_clients table as a JSON array and makes other changes to always check the allowed grant types the client can handle, RFC7591. Here is the list of grant types:

  • "authorization_code"
  • "personal_access"
  • "implicit"
  • "password"
  • "client_credentials"
  • "refresh_token"
  • "urn:ietf:params:oauth:grant-type:device_code" (to be implemented later)
  • "urn:ietf:params:oauth:grant-type:jwt-bearer" (not supported)
  • "urn:ietf:params:oauth:grant-type:saml2-bearer" (not supported)

For example, a client with 'grant_types' => ['authorization_code', 'refresh_token'] can handle "Authorization Code" and "Refresh Token" grant types.

Changes

  • grant_types column has been added on oauth_clients table as text (JSON array).
  • personal_access_client column of oauth_clients table has been removed.
  • password_client column of oauth_clients table has been removed.

Client Scopes

The scopes property was added to Client model on #1682 to limit scopes of a client. This PR adds scopes column to the oauth_clients table as a JSON array and makes other changes to always check for the scopes that a client can handle.

Changes

  • scopes column has been added on oauth_clients table as text (JSON array).
  • Just like the scopes on auth code and access token, 'scopes' => ['*'] allows all scopes.

Personal Access Token Model and Table

Since Passport 9.x and according to docs, you must set PASSPORT_PERSONAL_ACCESS_CLIENT_ID and PASSPORT_PERSONAL_ACCESS_CLIENT_SECRET environment variables to be able to use "Personal Access Token" grant. This PR removes redundant PersonalAccessClient model and oauth_personal_access_clients table.

Changes

  • PersonalAccessClient model has been removed.
  • oauth_personal_access_clients table has been removed.
  • Passport::$personalAccessClientModel property has been removed.
  • Passport::usePersonalAccessClientModel() method has been removed.
  • Passport::personalAccessClientModel() method has been removed.
  • Passport::personalAccessClient() method has been removed.

Miscellaneous Changes

  • php artisan passport:client --implicit command has been added.
  • php artisan passport:install doesn't create "Password Grant" client anymore, because it's deprecated.
  • UriRule has been rewritten to remove deprecated usage of \Illuminate\Contracts\Validation\Rule class.
  • RedirectRule has been removed, as we don't accept comma-separated redirect values anymore.

Upgrade Guide

Minimum Laravel Version

Laravel 10.0 is now the minimum required version.

Client Secret

Passport now always hashes clients' secret, you may call the following command if you need to hash your clients' secrets:

php artisan passport:hash

Client ID

Passport now uses UUID to identify clients. If you were already using client with UUIDs you don't have to make any changes, but if you were not using client with UUIDs you must change the type of client ID columns to char(36). Your previous client IDs won't change and you will get UUID for newly created clients from now on:

Schema::table('oauth_clients', function (Blueprint $table) {
    $table->char('id', 36)->change();
});

Schema::table('oauth_auth_codes', function (Blueprint $table) {
    $table->char('client_id', 36)->index()->change();
});

Schema::table('oauth_access_tokens', function (Blueprint $table) {
    $table->char('client_id', 36)->index()->change();
});

Clients Table

The oauth_clients table now requires grant_types, scopes and redirect_uris columns as JSON array and personal_access_client and password_client columns are removed:

Schema::table('oauth_clients', function (Blueprint $table) {
    $table->after('name', function (Blueprint $table) {
        $table->text('grant_types');
        $table->text('scopes');
        $table->text('redirect_uris');
    });
});

foreach (Passport::client()->cursor() as $client) {
    Model::withoutTimestamps(fn () => $client->forceFill([
        'grant_types' => match (true) {
            (bool) $client->personal_access_client => ['personal_access'],
            (bool) $client->password_client => ['password', 'refresh_token'],
            empty($client->secret) && ! empty($client->redirect) => ['authorization_code', 'implicit', 'refresh_token'],
            ! empty($client->secret) && empty($client->redirect) => ['client_credentials'],
            ! empty($client->secret) && ! empty($client->redirect) => ['authorization_code', 'implicit', 'refresh_token', 'client_credentials'],
            default => [],
        },
        'scopes' => ['*'],
        'redirect_uris' => explode(',', $client->redirect),
    ])->save());
}

Schema::table('oauth_clients', function (Blueprint $table) {
    $table->dropColumn(['redirect', 'personal_access_client', 'password_client']);
});

Removed functionalities

The following list of properties and methods have been removed:

  • Passport::$clientUuids property.
  • Passport::clientUuids() method.
  • Passport::setClientUuids() method.
  • 'passport.client_uuids' config property.
  • Passport::$hashesClientSecrets property.
  • Passport::hashClientSecrets() method.
  • Passport::$personalAccessClientModel property.
  • Passport::usePersonalAccessClientModel() method.
  • Passport::personalAccessClientModel() method.
  • Passport::personalAccessClient() method.
  • \Laravel\Passport\Client::authCodes() relation method.

Personal Access Token

If you are utilizing Personal Access tokens, make sure you have set client's ID and plain-text secret value in your application's .env file according to docs:

PASSPORT_PERSONAL_ACCESS_CLIENT_ID="client-id-value"
PASSPORT_PERSONAL_ACCESS_CLIENT_SECRET="unhashed-client-secret-value"

You may also drop oauth_personal_access_clients table:

Schema::dropIfExists('oauth_personal_access_clients');

@hafezdivandari hafezdivandari marked this pull request as draft May 18, 2024 19:28
@hafezdivandari hafezdivandari marked this pull request as ready for review May 19, 2024 13:52
@taylorotwell
Copy link
Member

A lot of breaking changes here and I'm not sure the ROI is there to support them.

@taylorotwell taylorotwell marked this pull request as draft May 20, 2024 13:29
@hafezdivandari
Copy link
Contributor Author

hafezdivandari commented May 20, 2024

@taylorotwell This PR actually make this package easier to maintain and support by removing some deprecations and unnecessary configurations.

Please let us know what the community can do to make Passport profitable in the way you prefer, e.g. I wanted to make Passport compatible with Laravel Jetstream / Fortify. We can also change its logo just like other Laravel first-party packages, etc.

Many people are using Laravel to develop APIs and OAuth2 is a must in most scenarios. Sanctum is great, but you know the difference better than me.

We have already added support for v9 of the OAuth2 Server (#1734). The latest version adds support for "Device Authorization Flow" RFC8628; I've already prepared a PR to support that on Passport that I will send after this one.

cc @driesvints

@driesvints
Copy link
Member

Hi @hafezdivandari. We really appreciate all of this work! But like Taylor said, I also feel this is a bit too much... The changes in this PR all seem sound to me but impose a hefty upgrade path on users, something we at Laravel try to avoid at all cost. There for, I feel we should cut on some of these changes.

I would:

  • At the very least send in separate PR's for all of these changes as doing it in a single PR is just too difficult to review for Taylor.
  • Not do the following changes: Client ID, Client Redirect URIs, Client Grant Types, Client Scopes & Personal Access Token Model and Table. These take up the bulk of the upgrade steps and aren't 100% needed imo. Also passport:client --implicit isn't needed since that grant type is no longer recommended.
  • Still do: Client Secret, Laravel v10 bump, remove "Password Grant" from install command and also the --password flag, do the UriRule rewrite (and also same changes for RedirectRule).

I realise this will cut a lot of the work you made but this will make the transition for users much more feasible.

@hafezdivandari
Copy link
Contributor Author

hafezdivandari commented May 21, 2024

Hi @driesvints, thanks for your reply. I should have sent these changes as separate PRs, but I thought it would be hard to guess why each one is needed without knowing the whole picture. I'll resend separately, but please keep this one open as draft for a while.

@stanliwise
Copy link

Finally, We are going to have a compatible OAuth Framework. The optional hash part of Passport is a security concern. User should instead need to specify they don't want it hashed rather than specify they wanted it hashed.

@hafezdivandari
Copy link
Contributor Author

@stanliwise you may check this #1745

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.

None yet

5 participants