Skip to content

Commit

Permalink
Merge pull request #12 from DirectoryTree/provider-tokens
Browse files Browse the repository at this point in the history
Add ability to store Socialite provider tokens
  • Loading branch information
stevebauman authored Oct 28, 2024
2 parents 9c70549 + cfbf422 commit 26abdc8
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 6 deletions.
69 changes: 66 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ Almost everything in Bartender can be customized.
- [Installation](#installation)
- [Setup](#setup)
- [Usage](#usage)
- [Soft Deletes](#soft-deletes)
- [Email Verification](#email-verification)
- [Access/Refresh Tokens](#accessrefresh-tokens)
- [Extending & Customizing](#extending--customizing)

## Requirements
Expand All @@ -41,9 +44,14 @@ You can install the package via composer:
composer require directorytree/bartender
```

Then, publish the migration:
Then, publish the migrations. They will create the required columns on the `users` table:

> It creates the `provider_id` and `provider_name` column on the `users` table.
- `provider_id`
- `provider_name`
- `provider_access_token`
- `provider_refresh_token`

> If your application does not need to store/access provider tokens, you may delete the `2024_10_27_131354_add_provider_token_columns_to_users_table.php` migration.
```bash
php artisan vendor:publish --provider="DirectoryTree\Bartender\BartenderServiceProvider"
Expand Down Expand Up @@ -164,10 +172,63 @@ To change this behaviour, [swap out the repository](#user-creation--updating).

### Email Verification

With the default `UserProviderRepository`, users will emails will be automatically verified (via the `email_verified_at` column) if it is not already set.
With the default `UserProviderRepository`, users with emails will be automatically verified (via the `email_verified_at` column) if it is not already set.

To change this behaviour, [swap out the repository](#user-creation--updating).

### Access/Refresh Tokens

To enable storing the authentication provider access and refresh tokens
on your user so that you can access them later, you may apply the
`StoresProviderTokens` interface on your model:

```php
// app/Models/User.php

namespace App\Models;

use DirectoryTree\Bartender\StoresProviderTokens;

class User extends Authenticatable implements StoresProviderTokens
{
// ...
}
```

You may also want to add these columns to your model's `$hidden` attributes, as well as `encrypted` casts for additional security:

```php
// app/Models/User.php

class User extends Authenticatable implements StoresProviderTokens
{
/**
* The attributes that should be hidden for serialization.
*/
protected $hidden = [
'provider_access_token',
'provider_refresh_token'
];

/**
* Get the attributes that should be cast.
*/
protected function casts(): array
{
return [
'provider_access_token' => 'encrypted',
'provider_refresh_token' => 'encrypted',
];
}
}
```

Otherwise, if you do not need to store these tokens, you are free to delete the
published `2024_10_27_131354_add_provider_token_columns_to_users_table.php`
migration file and omit applying the `StoresProviderTokens` interface.
Bartender will skip storing these tokens as it does not
require them to successfully authenticate users.

## Extending & Customizing

Almost everything can be swapped out in Bartender.
Expand Down Expand Up @@ -242,6 +303,8 @@ You may also extend the built-in `UserProviderHandler` implementation if you pre
For example, if you need to adjust the scopes for a single provider:

```php
// app/Socialite/MicrosoftUserHandler.php

namespace App\Socialite;

use Illuminate\Http\RedirectResponse;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->after('provider_name', function (Blueprint $table) {
$table->string('provider_access_token')->nullable();
$table->string('provider_refresh_token')->nullable();
});
});
}

/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn([
'provider_access_token',
'provider_refresh_token',
]);
});
}
};
1 change: 1 addition & 0 deletions src/BartenderServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public function boot(): void
{
$this->publishes([
__DIR__.'/../database/migrations/2024_03_31_000001_add_provider_columns_to_users_table.php' => database_path('migrations/2024_03_31_000001_add_provider_columns_to_users_table.php'),
__DIR__.'/../database/migrations/2024_10_27_131354_add_provider_token_columns_to_users_table.php' => database_path('migrations/2024_10_27_131354_add_provider_token_columns_to_users_table.php'),
], 'bartender-migrations');
}

Expand Down
5 changes: 5 additions & 0 deletions src/StoresProviderTokens.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php

namespace DirectoryTree\Bartender;

interface StoresProviderTokens {}
27 changes: 25 additions & 2 deletions src/UserProviderRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,12 @@ public function updateOrCreate(string $driver, SocialiteUser $user): Authenticat
: [],
$this->isVerifyingEmails($model)
? ['email_verified_at' => $eloquent->email_verified_at ?? now()]
: []
: [],
$this->isStoringTokens($model)
? [
'provider_access_token' => $user->token,
'provider_refresh_token' => $this->getRefreshToken($user, $eloquent->provider_refresh_token),
] : [],
)
)->save();

Expand All @@ -77,6 +82,16 @@ protected function getNewPassword(): string
return Str::random();
}

/**
* Get the refresh token from the Socialite user.
*/
protected function getRefreshToken(SocialiteUser $user, ?string $default = null): ?string
{
return $user->refreshToken
?? $user->tokenSecret
?? $default;
}

/**
* Get a new user query instance.
*
Expand All @@ -92,7 +107,15 @@ protected function newUserQuery(string $model): Builder
}

/**
* Determine if the given model uses soft deletes.
* Determine if the given model is storing Socialite tokens.
*/
protected function isStoringTokens(string $model): bool
{
return in_array(StoresProviderTokens::class, class_implements($model));
}

/**
* Determine if the given model is using soft deletes.
*/
protected function isUsingSoftDeletes(string $model): bool
{
Expand Down
3 changes: 2 additions & 1 deletion tests/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

namespace DirectoryTree\Bartender\Tests;

use DirectoryTree\Bartender\StoresProviderTokens;
use Illuminate\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
class User extends Authenticatable implements StoresProviderTokens
{
use MustVerifyEmail;

Expand Down
6 changes: 6 additions & 0 deletions tests/UserProviderRepositoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
$user->id = '1';
$user->name = 'foo';
$user->email = '[email protected]';
$user->token = '1234';
$user->refreshToken = '2345';
});

$user = (new UserProviderRepository)->updateOrCreate('foo', $socialite);
Expand All @@ -57,6 +59,8 @@
expect($user->email)->toBe('[email protected]');
expect($user->provider_id)->toBe('1');
expect($user->provider_name)->toBe('foo');
expect($user->provider_access_token)->toBe('1234');
expect($user->provider_refresh_token)->toBe('2345');
});

it('updates user not associated to provider', function () {
Expand All @@ -81,6 +85,8 @@
expect($user->email)->toBe('[email protected]');
expect($user->provider_id)->toBe('1');
expect($user->provider_name)->toBe('foo');
expect($user->provider_access_token)->toBeNull();
expect($user->provider_refresh_token)->toBeNull();
});

it('throws exception when attempting to create existing user with null provider', function () {
Expand Down

0 comments on commit 26abdc8

Please sign in to comment.