├── .gitignore ├── routes └── api.php ├── config └── hashed-passport.php ├── phpunit.xml.dist ├── src ├── HashedPassport.php ├── Traits │ ├── HashesIds.php │ └── HandlesEncryptedSecrets.php ├── Middleware │ └── DecodeHashedClientIdOnRequest.php ├── Commands │ ├── Install.php │ └── Uninstall.php ├── Observers │ └── ClientObserver.php └── HashedPassportServiceProvider.php ├── database └── migrations │ └── 2018_03_30_182458_oauth_clients_secret_change_varchar_length.php ├── LICENSE.txt ├── tests ├── HashingTest.php ├── EncryptionTest.php └── TestCase.php ├── composer.json └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | /composer.lock 2 | /vendor 3 | -------------------------------------------------------------------------------- /routes/api.php: -------------------------------------------------------------------------------- 1 | middleware(['throttle', 'hashed_passport']); 5 | -------------------------------------------------------------------------------- /config/hashed-passport.php: -------------------------------------------------------------------------------- 1 | env('APP_KEY'), 19 | 20 | ]; 21 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | ./tests/ 16 | 17 | 18 | 19 | 20 | ./src/ 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/HashedPassport.php: -------------------------------------------------------------------------------- 1 | encode($id); 26 | } 27 | 28 | /** 29 | * UnHashes the hashed client_id into the auto-incrementing integer 30 | * 31 | * @param $client_id 32 | * 33 | * @return mixed 34 | */ 35 | protected function decode($client_id) 36 | { 37 | return Hashids::connection('hashed_passport')->decode($client_id); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Middleware/DecodeHashedClientIdOnRequest.php: -------------------------------------------------------------------------------- 1 | offsetExists('client_id')) { 21 | $client_id = $request->offsetGet('client_id'); 22 | 23 | if (!is_numeric($client_id)) { 24 | $result = $this->decode($client_id); 25 | 26 | if (count($result) > 0) { 27 | $request->offsetSet('client_id', $result[0]); 28 | } else { 29 | $request->offsetSet('client_id', -1); 30 | } 31 | } 32 | } 33 | 34 | return $next($request); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /database/migrations/2018_03_30_182458_oauth_clients_secret_change_varchar_length.php: -------------------------------------------------------------------------------- 1 | string('secret', 2048)->change(); 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | * 27 | * @return void 28 | */ 29 | public function down() 30 | { 31 | /** 32 | * Change the value back to it's default value of 100 33 | */ 34 | Schema::table('oauth_clients', function (Blueprint $table) { 35 | $table->string('secret', 100)->change(); 36 | }); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /tests/HashingTest.php: -------------------------------------------------------------------------------- 1 | client = $this->createTestClient(); 19 | } 20 | 21 | /** @test */ 22 | public function adds_client_id_to_model_when_loading() 23 | { 24 | // Given 25 | // When 26 | // Then 27 | $this->assertArrayHasKey('client_id', $this->client->toArray()); 28 | } 29 | 30 | /** @test */ 31 | public function hashes_client_id() 32 | { 33 | // Given 34 | $database_client = DB::table($this->client->getTable())->where('id', '=', $this->client->id)->first(); 35 | 36 | // When 37 | $this->assertNotEquals($database_client->id, $this->client->client_id); 38 | 39 | // And when decoded, they're the same 40 | $this->assertEquals($database_client->id, $this->decode($this->client->client_id)[0]); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Commands/Install.php: -------------------------------------------------------------------------------- 1 | encrypt_client_secrets(); 44 | $this->secrets_encrypted(); 45 | } 46 | 47 | $this->info('Hashed Passport installation completed.'); 48 | $this->info(''); 49 | $this->info(''); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Commands/Uninstall.php: -------------------------------------------------------------------------------- 1 | decrypt_client_secrets(); 44 | $this->secrets_decrypted(); 45 | } 46 | 47 | $this->info('Hashed-passport removal completed.'); 48 | $this->info(''); 49 | $this->info('You can now safely run:'); 50 | $this->info('composer remove ssmulders/hashed-passport'); 51 | $this->info(''); 52 | $this->info(''); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ssmulders/hashed-passport", 3 | "description": "Transforms Laravel Passport's default incrementing integer client_id into an industry standard unique hashed string. Optionally, you can use encrypted client secrets for improved security. The package is non-intrusive. See the readme for details.", 4 | "type": "library", 5 | "license": "MIT", 6 | "support": { 7 | "issues": "https://github.com/ssmulders/hashed-passport/issues", 8 | "source": "https://github.com/ssmulders/hashed-passport" 9 | }, 10 | "authors": [ 11 | { 12 | "name": "Stan Smulders", 13 | "email": "stansmulders@gmail.com" 14 | } 15 | ], 16 | "require": { 17 | "laravel/passport": "^7.0", 18 | "vinkla/hashids": ">=5.0 <8.0", 19 | "doctrine/dbal": "^2.6" 20 | }, 21 | "require-dev": { 22 | "mockery/mockery": "~1.0", 23 | "orchestra/testbench": "~3.7", 24 | "phpunit/phpunit": ">=7.5 <9.0" 25 | }, 26 | "autoload": { 27 | "psr-4": { 28 | "Ssmulders\\HashedPassport\\": "src/" 29 | } 30 | }, 31 | "autoload-dev": { 32 | "psr-4": { 33 | "Ssmulders\\HashedPassport\\Tests\\": "tests/" 34 | } 35 | }, 36 | "extra": { 37 | "branch-alias": { 38 | "dev-master": "1.1-dev" 39 | }, 40 | "laravel": { 41 | "providers": [ 42 | "Ssmulders\\HashedPassport\\HashedPassportServiceProvider" 43 | ] 44 | } 45 | }, 46 | "config": { 47 | "sort-packages": true 48 | }, 49 | "minimum-stability": "dev", 50 | "prefer-stable": true 51 | } 52 | -------------------------------------------------------------------------------- /src/Observers/ClientObserver.php: -------------------------------------------------------------------------------- 1 | setAttribute('client_id', $this->encode($oauth_client->getAttribute('id'))); 25 | $oauth_client->setAttribute('client_id', $this->encode($oauth_client->getAttribute('id'))); 26 | 27 | if (HashedPassport::$withEncryption) { 28 | $oauth_client->setAttribute('secret', decrypt($oauth_client->getAttribute('secret'))); 29 | } 30 | } 31 | 32 | /** 33 | * Encrypt the Client Secret before storing it. 34 | * 35 | * @param Client $oauth_client 36 | */ 37 | public function saving(Client $oauth_client) 38 | { 39 | // Prevent trying to save hashed client ID to the database. 40 | $oauth_client->offsetUnset('client_id'); 41 | 42 | if (HashedPassport::$withEncryption) { 43 | $oauth_client->setAttribute('secret', encrypt($oauth_client->getAttribute('secret'))); 44 | } 45 | } 46 | 47 | /** 48 | * Hash the Client ID and encrypt secret after saving. 49 | * 50 | * @param Client $oauth_client 51 | */ 52 | public function saved(Client $oauth_client) 53 | { 54 | $this->retrieved($oauth_client); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/EncryptionTest.php: -------------------------------------------------------------------------------- 1 | client = $this->createTestClient(); 22 | } 23 | 24 | /** @test */ 25 | public function database_secret_is_stored_encrypted() 26 | { 27 | // Given 28 | $database_client = DB::table($this->client->getTable())->where('id', '=', $this->client->id)->first(); 29 | 30 | $application_secret = $this->client->secret; 31 | $database_secret = $database_client->secret; 32 | 33 | // Checks with and without encryption 34 | // When disabled in AppServiceProvider it uses the else clause. 35 | if (HashedPassport::$withEncryption) { 36 | // Make sure that what's stored in the DB isn't the same as what's being used in the application 37 | $this->assertNotSame($application_secret, $database_secret); 38 | 39 | // Then when we decrypt the secret, it is the same. 40 | $this->assertSame($application_secret, decrypt($database_secret)); 41 | } else { 42 | $this->assertSame($application_secret, $database_secret); 43 | } 44 | } 45 | 46 | /** @test */ 47 | public function enables_encryption_at_will() 48 | { 49 | // Given 50 | $hashed_passport = HashedPassport::withEncryption(); 51 | 52 | // Then 53 | $this->assertTrue($hashed_passport::$withEncryption); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | artisan('migrate', ['--database' => 'testbench']); 19 | } 20 | 21 | protected function getPackageProviders($app) 22 | { 23 | return [ 24 | PassportServiceProvider::class, 25 | HashidsServiceProvider::class, 26 | HashedPassportServiceProvider::class, 27 | ]; 28 | } 29 | 30 | /** 31 | * Define environment setup. 32 | * 33 | * @param \Illuminate\Foundation\Application $app 34 | * 35 | * @return void 36 | */ 37 | protected function getEnvironmentSetUp($app) 38 | { 39 | // Generate encryption key 40 | $app['config']->set('app.key', 'base64:' . base64_encode(Encrypter::generateKey($app['config']['app.cipher']))); 41 | 42 | // Setup default database to use sqlite :memory: 43 | $app['config']->set('database.default', 'testbench'); 44 | $app['config']->set('database.connections.testbench', [ 45 | 'driver' => 'sqlite', 46 | 'database' => ':memory:', 47 | 'prefix' => '', 48 | ]); 49 | } 50 | 51 | protected function createTestClient() 52 | { 53 | return Passport::client()->create([ 54 | 'name' => 'Test Client', 55 | 'secret' => Str::random(40), 56 | 'redirect' => '', 57 | 'personal_access_client' => false, 58 | 'password_client' => false, 59 | 'revoked' => false, 60 | ])->fresh(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/HashedPassportServiceProvider.php: -------------------------------------------------------------------------------- 1 | set_salt(); 24 | 25 | $this->register_middleware($router); 26 | 27 | $this->register_observer(); 28 | 29 | $this->load_routes(); 30 | 31 | if ($this->app->runningInConsole()) { 32 | $this->register_console_commands_and_migrations(); 33 | 34 | $this->publishes([ 35 | __DIR__ . '/../config/hashed-passport.php' => config_path('hashed-passport.php'), 36 | ], 'config'); 37 | } 38 | } 39 | 40 | /** 41 | * Register services. 42 | * 43 | * @return void 44 | */ 45 | public function register() 46 | { 47 | $this->mergeConfigFrom( 48 | __DIR__ . '/../config/hashed-passport.php', 'hashed-passport' 49 | ); 50 | } 51 | 52 | /* 53 | |-------------------------------------------------------------------------- 54 | | Helpers 55 | |-------------------------------------------------------------------------- 56 | | 57 | | To keep things cleaner 58 | | 59 | */ 60 | 61 | /** 62 | * Registers the observer that handles the hashed client_id 63 | */ 64 | private function register_observer() 65 | { 66 | $client = Passport::clientModel(); 67 | $client::observe(ClientObserver::class); 68 | } 69 | 70 | /** 71 | * Overwrites the Passport routes after the app has loaded to ensure these are used. 72 | */ 73 | private function load_routes() 74 | { 75 | $this->app->booted(function () { 76 | $this->loadRoutesFrom(__DIR__ . '/../routes/api.php'); 77 | }); 78 | } 79 | 80 | /** 81 | * Adds the encryption commands and migrations. 82 | */ 83 | private function register_console_commands_and_migrations() 84 | { 85 | /** 86 | * Upgrades the secret column's max length from 100 to 2048 characters to support encrypted values. 87 | * Enables the manual encrypting and decrypting of the client secrets 88 | */ 89 | if (Passport::$runsMigrations && HashedPassport::$withEncryption) { 90 | $this->loadMigrationsFrom(__DIR__ . '/../database/migrations'); 91 | 92 | $this->commands([ 93 | Install::class, 94 | Uninstall::class, 95 | ]); 96 | } 97 | } 98 | 99 | /** 100 | * Add the Hashids salt with the APP_KEY so it's unique, but constant 101 | */ 102 | private function set_salt() 103 | { 104 | $this->app['config']['hashids.connections.hashed_passport'] = [ 105 | 'salt' => config('hashed-passport.salt'), 106 | 'length' => '32', 107 | ]; 108 | } 109 | 110 | /** 111 | * The middleware magic 112 | * 113 | * Catches both incoming and outgoing requests and should be compatible with custom routes 114 | */ 115 | private function register_middleware(Router $router) 116 | { 117 | $router->middlewareGroup('hashed_passport', [ 118 | Middleware\DecodeHashedClientIdOnRequest::class, 119 | ]); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/Traits/HandlesEncryptedSecrets.php: -------------------------------------------------------------------------------- 1 | oauth_clients() as $oauth_client) { 25 | try { 26 | $decrypted_secret = decrypt($oauth_client->secret); 27 | \DB::table('oauth_clients')->where('id', $oauth_client->id) 28 | ->update(['secret' => $decrypted_secret]); 29 | } catch (DecryptException $e) { 30 | $this->write_to_console('Decryption for oauth_client with id: ' . $oauth_client->id . 31 | ' FAILED. Could be it was already decrypted.'); 32 | $this->write_to_console('Stored secret:'); 33 | $this->write_to_console($oauth_client->secret); 34 | } 35 | } 36 | } 37 | 38 | /** 39 | * Walk through all current oauth_clients using the DB facade to bypass the Observer and make sure 40 | * that all entries are saved with encryption. 41 | */ 42 | private function encrypt_client_secrets() 43 | { 44 | foreach ($this->oauth_clients() as $oauth_client) { 45 | \DB::table('oauth_clients')->where('id', $oauth_client->id) 46 | ->update(['secret' => encrypt($oauth_client->secret)]); 47 | } 48 | } 49 | 50 | /* 51 | |-------------------------------------------------------------------------- 52 | | Helpers 53 | |-------------------------------------------------------------------------- 54 | | 55 | | 56 | | 57 | | 58 | */ 59 | 60 | private function secrets_encrypted() 61 | { 62 | $this->write_to_console(" "); 63 | $this->write_to_console(" "); 64 | $this->write_to_console(" "); 65 | $this->write_to_console(" .---------------. "); 66 | $this->write_to_console(" / .-------------. \ "); 67 | $this->write_to_console(" / / \ \ "); 68 | $this->write_to_console(" | | | | "); 69 | $this->write_to_console(" _| |_______________| |_ "); 70 | $this->write_to_console(" .' |_| |_| '. "); 71 | $this->write_to_console(" '._____ ___________ _____.' "); 72 | $this->write_to_console(" | .'___________'. | "); 73 | $this->write_to_console(" '.__.'.' '.'.__.' "); 74 | $this->write_to_console(" '. | secrets | .' "); 75 | $this->write_to_console(" '.__ | encrypted | __.' "); 76 | $this->write_to_console(" | '.'.___________.'.' | "); 77 | $this->write_to_console(" '.____'.___________.'____.' "); 78 | $this->write_to_console(" '._______________________.' "); 79 | $this->write_to_console(" "); 80 | $this->write_to_console(" hashed-passport encryption active "); 81 | $this->write_to_console(" "); 82 | $this->write_to_console(" "); 83 | $this->write_to_console(" "); 84 | } 85 | 86 | private function secrets_decrypted() 87 | { 88 | $this->write_to_console(" "); 89 | $this->write_to_console(" "); 90 | $this->write_to_console(" "); 91 | $this->write_to_console(" .---------------. "); 92 | $this->write_to_console(" / .-------------. \ "); 93 | $this->write_to_console(" / / \ \ "); 94 | $this->write_to_console(" | | | | "); 95 | $this->write_to_console(" | | | | "); 96 | $this->write_to_console(" | | |_/ "); 97 | $this->write_to_console(" | | "); 98 | $this->write_to_console(" _| |___________________ "); 99 | $this->write_to_console(" .' |_| \_\ '. "); 100 | $this->write_to_console(" '._____ ___________ _____.' "); 101 | $this->write_to_console(" | .'___________'. | "); 102 | $this->write_to_console(" '.__.'.' '.'.__.' "); 103 | $this->write_to_console(" '. | secrets | .' "); 104 | $this->write_to_console(" '.__ | decrypted | __.' "); 105 | $this->write_to_console(" | '.'.___________.'.' | "); 106 | $this->write_to_console(" '.____'.___________.'____.' "); 107 | $this->write_to_console(" '._______________________.' "); 108 | $this->write_to_console(" "); 109 | $this->write_to_console(" hashed-passport encryption deactivated "); 110 | $this->write_to_console(" "); 111 | $this->write_to_console(" "); 112 | $this->write_to_console(" "); 113 | } 114 | 115 | /** 116 | * Writes to the console like an Artisan Command. 117 | * 118 | * @param $message 119 | */ 120 | private function write_to_console($message) 121 | { 122 | $output = new \Symfony\Component\Console\Output\ConsoleOutput(); 123 | 124 | $output->writeln($message); 125 | } 126 | 127 | /* 128 | |-------------------------------------------------------------------------- 129 | | Getters and Setters 130 | |-------------------------------------------------------------------------- 131 | | 132 | | 133 | | 134 | | 135 | */ 136 | 137 | /** 138 | * @var $oauth_clients Collection containing all the oauth clients. 139 | */ 140 | protected $oauth_clients; 141 | 142 | /** 143 | * Gets clients 144 | * 145 | * @return mixed 146 | */ 147 | protected function oauth_clients() 148 | { 149 | return \DB::table('oauth_clients')->get(); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | Hashed Passport 2 | ----- 3 | Hashed Passport turns Passport's public Client IDs from this: 4 | 5 | `client_id: 1` 6 | 7 | into the more pleasing, beautiful and industry standard: 8 | 9 | `client_id: AE20JvpmdYx34wD789Lng5jyqelQar8R` 10 | 11 | While not touching any of the core Passport files. It uses a middleware decode the `client_id` on routes and an observer to make the hashed id available through the `client_id` parameter of the `\Laravel\Passport\Client` anywhere in the application. 12 | 13 | Encryption of the client secrets is optional. These are saved in plain-text by default. After enabling this feature Hashed Passport turns a database entry of `wOVl4sBrTU46KwaiV56yc9IftikEIcKfWYCpwosG` 14 | 15 | into 16 | 17 | `eyJpdiI6IkhDQlYyZDBpeUVCVHRsZGFcL3ZiejRBPT0iLCJ2YWx1ZSI6InFoUGRKcUFRaVwvc2t3Q1ZhVHhqM3lpaW05cm1FaXpObUtyNmd4QXNMU21mVmNhNW45N0lVTHJLa2prYlJpcmpnQzJqMTRXS1c3NWlaR2tcL01ZZmFNXC9RPT0iLCJtYWMiOiJhYzJmOTMwZWE2NTI4MWZiMTAxNDg5NTQ2NmFiNDU2YmZmOTcxOTIzMTVmNTU2Njk1N2ZlNzg5MzFiNmI5MTUzIn0=` 18 | 19 | Introduction 20 | ----- 21 | 22 | The idea for this package originated when I first started working with Laravel Passport. Having made a few APIs using Lumen and JWT it was jarring to discover that Passport used the auto-increment integer as the Client ID. 23 | 24 | The pros and cons are all listed in this 2 year old issue. You can tell just _how_ old it is by looking at the ID of the issue ;-) 25 | 26 | [Original Laravel/Passport Github Thread](https://github.com/laravel/passport/issues/14) 27 | 28 | Changelog 29 | --- 30 | * 1.0 - Non-intrusive implementation of hashed Passport client IDs. 31 | * 1.1 - Non-intrusive implementation of encrypted Passport client secrets. 32 | * 1.2 - Added the option to customise the hashing salt, cleaned up the code and improved the readme. 33 | * 2.0 - Works with Laravel Passport 7.0 and includes tests. Major thanks to Hackel and other contributors. 34 | 35 | Requirements 36 | ----- 37 | 38 | **OPTIONAL: Encrypted Secrets** 39 | * A database supporting `VARCHAR(2048)`. If you use MySQL this means version 5.0.3+ 40 | 41 | 42 | Installation 43 | ----- 44 | 45 | ##### Hashed Client IDs 46 | 47 | * Install via composer `composer require ssmulders/hashed-passport` 48 | 49 | That's it! If you haven't tinkered with any default Laravel Passport routes, you're all set. 50 | 51 | ##### OPTIONAL: Encrypted Secrets 52 | * Add `HashedPassport::withEncryption();` to the register method of your `AppServiceProvider`. 53 | 54 | * Run the `php artisan migrate` 55 | 56 | * Run `php artisan hashed_passport:install` 57 | 58 | Voila! Your secrets are now stored with encryption and automatically decrypted after a DB retrieval. 59 | 60 | ##### OPTIONAL: Overwrite the APP_KEY Hashing Salt 61 | * Run `php artisan vendor:publish --provider="Ssmulders\HashedPassport\HashedPassportServiceProvider"` 62 | 63 | Now update the `salt` key in `config/hashed-passport.php` with a custom string. 64 | 65 | Further Usage and Customisation 66 | ----- 67 | 68 | Anywhere you access the `Laravel\Passport\Client` model a `client_id` parameter is made available. This is the hashed client id. 69 | 70 | #### Add Middleware to additional Routes 71 | After installation `/oauth/token` route will now accept both the the hashed id and the regular integer id on incoming requests. 72 | 73 | To add this functionality to any other route, just attach the `hashed_passport` middleware like so: 74 | 75 | `Route::get('/oauth/clients', '\Laravel\Passport\Http\Controllers\ClientController@update')->middleware(['hashed_passport']);` 76 | 77 | The incoming hashed `client_id` string will now automatically be converted to it's integer value before being processed further by the application. 78 | 79 | #### Change the hash salt 80 | By default the hashing salt is the `APP_KEY`. To override this behaviour publish the config file to set a custom salt: 81 | 82 | `php artisan publish:config` 83 | 84 | How it works 85 | ----- 86 | 87 | #### Hashed ID 88 | In order to work out-of-the-box the package overwrites the oAuth2 token route to accept the Client ID as a hashed string. Further more the hashed client id is made available on all Client models through the `client_id` parameter thanks to the `ClientObserver`. This same `ClientObserver` takes care of the encrypting and decrypting of the client secret (when enabled). 89 | 90 | The hashed id is still based on the `oauth_clients` table's `index` column. All it does is transform that ugly integer into a beautiful hashed string, Cinderella style. 91 | 92 | To do this a new connection is added to Hashids called `client_id`. This salt is based on your `APP_KEY` (unless configured otherwise). 93 | 94 | Supported grant types: 95 | 96 | `password`: **verified** 97 | 98 | `client_credentials`: **verified** 99 | 100 | `authorization_code`: **untested (probably needs the middleware added to other routes)** 101 | 102 | ##### Encrypted Secret 103 | Similar to the hashed ID, the observer catches the `Client` when it's being saved and retrieved from the database. 104 | 105 | Since the default `VARCHAR(100)` value for the `secret` column is insufficient for storing the lengthier encrypted secrets, that needs to be updated to `VARCHAR(2048)`. It could be less, but I'd rather be safe than sorry. The actual maximum column character length has no impact on storage usage. 106 | 107 | Running `php artisan hashed_passport:install` will then encrypt all the existing rows so their secret is encrypted. Once successful, you'll be greeted with a large lock. From now on all your client secrets are stored safely with Laravel's `encrypt()` helper. 108 | 109 | 110 | 111 | Troubleshooting 112 | ----- 113 | 114 | * The package should be compatible with any installation and can even be used with projects that are using the integer client_id in production, as it will support both versions of the client_id and encrypt the secrets of all clients. 115 | 116 | * Check your routes to make sure that `/oauth/token` uses the `hashed_passport` middleware: 117 | `php artisan route:list`. The same applies for any other routes you want to accept requests with hashed `client_id` from. 118 | 119 | 120 | Uninstall 121 | ----- 122 | 123 | Should support for hashed IDs and encrypted secrets be added to Laravel Passport in the future, all you'd need to do in order to revert back to normal is follow these steps: 124 | 125 | Run `php artisan hashed_passport:uninstall` to revert back to plain text secrets in your database. 126 | 127 | Run `composer remove ssmulders/hashed-passport` to remove the package. 128 | 129 | Remove `HashedPassport::withEncryption();` from your `AppServiceProvider` 130 | 131 | Run this SQL command `DELETE FROM 'migrations' WHERE migration LIKE '%hashed_passport%'` to update your migrations table and prevent rollback errors. `php artisan migrate:refresh` will work fine though. 132 | 133 | Feedback 134 | --- 135 | 136 | It's my first ever package so if you have any feedback on whatever, please leave a message! Learning how to create a package has opened up a whole new bag of appreciation for the wonder that's known as Laravel. 137 | 138 | Feel free to open pull request(s) that improve on the functionality, or fix any bugs. 139 | 140 | Credits 141 | --- 142 | Thanks to [@hfmikep](https://github.com/hfmikep) and [@nikkuang](https://github.com/nikkuang) for being the first to properly start tackling this long-standing feature request. And once again to [@hfmikep](https://github.com/hfmikep) for for creating the code that's used in the `UnHashClientIdOnRequest` middleware. And to [@corbosman](https://github.com/corbosman) for requesting the client secret encryption. 143 | 144 | License 145 | --- 146 | Copyright (c) 2018 Stan Smulders 147 | 148 | MIT License 149 | 150 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 151 | 152 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 153 | 154 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 155 | 156 | --------------------------------------------------------------------------------