Browse Source

Move away from storing token + id data on user, use separate token

object
master
Adam Pippin 3 years ago
parent
commit
ecd03da851
  1. 35
      database/migrations/authkit2_token.php
  2. 10
      database/migrations/existing/authkit2_users_update_minimal.php
  3. 10
      database/migrations/new/authkit2_users_update.php
  4. 18
      src/Events/UserLogin.php
  5. 77
      src/Http/Controllers/AuthenticationController.php
  6. 9
      src/Models/IAuthkitUser.php
  7. 20
      src/Models/Token.php
  8. 29
      src/Models/User.php
  9. 43
      src/Observers/UserObserver.php
  10. 12
      src/Providers/Authkit2ServiceProvider.php
  11. 11
      src/Providers/AuthnServiceProvider.php

35
database/migrations/authkit2_token.php

@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class Authkit2Token extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
// These are split up like this as to not cause issues when running
// against sqlite.
Schema::create('authkit2_token', static function(Blueprint $table) {
$table->text('id')->unique();
$table->foreignId('user_id')->constrained('users');
$table->text('access_token');
$table->text('refresh_token');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop('authkit2_token');
}
}

10
database/migrations/existing/authkit2_users_update_minimal.php

@ -6,7 +6,7 @@ use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class Authkit2UsersUpdateMinimal extends Migration
class Authkit2ExistingProject extends Migration
{
/**
* Run the migrations.
@ -15,16 +15,9 @@ class Authkit2UsersUpdateMinimal extends Migration
*/
public function up()
{
// These are split up like this as to not cause issues when running
// against sqlite.
Schema::table('users', static function(Blueprint $table) {
$table->string('password')->nullable()->change();
});
Schema::table('users', static function(BluePrint $table) {
$table->string('authkit_id')->unique()->nullable();
$table->text('authkit_access_token')->nullable();
$table->text('authkit_refresh_token')->nullable();
});
}
/**
@ -35,7 +28,6 @@ class Authkit2UsersUpdateMinimal extends Migration
public function down()
{
Schema::table('users', static function(Blueprint $table) {
$table->dropColumn(['authkit_id', 'authkit_access_token', 'authkit_refresh_token']);
$table->string('password')->nullable(false)->default(null)->change();
});
}

10
database/migrations/new/authkit2_users_update.php

@ -6,7 +6,7 @@ use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class Authkit2UsersUpdate extends Migration
class Authkit2FreshProject extends Migration
{
/**
* Run the migrations.
@ -15,16 +15,9 @@ class Authkit2UsersUpdate extends Migration
*/
public function up()
{
// These are split up like this as to not cause issues when running
// against sqlite.
Schema::table('users', static function(Blueprint $table) {
$table->dropColumn(['email_verified_at', 'password']);
});
Schema::table('users', static function(Blueprint $table) {
$table->string('authkit_id')->unique();
$table->text('authkit_access_token');
$table->text('authkit_refresh_token');
});
}
/**
@ -35,7 +28,6 @@ class Authkit2UsersUpdate extends Migration
public function down()
{
Schema::table('users', static function(Blueprint $table) {
$table->dropColumn(['authkit_id', 'authkit_access_token', 'authkit_refresh_token']);
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
});

18
src/Events/UserLogin.php

@ -9,4 +9,22 @@ namespace authkit2\Events;
*/
class UserLogin extends UserEvent
{
/**
* Additional fields returned during login
*
* @var mixed
*/
public $user_info;
/**
* Initialize new event
*
* @param mixed $user
* @param array<string,string> $user_info
*/
public function __construct($user, array $user_info)
{
parent::__construct($user);
$this->user_info = $user_info;
}
}

77
src/Http/Controllers/AuthenticationController.php

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace authkit2\Http\Controllers;
use Illuminate\Http\Request;
use authkit2\Models\Token;
/**
* Methods for handling user authentication operations
@ -43,75 +44,49 @@ class AuthenticationController extends Controller
*/
public function callback(Request $request)
{
// Get the user class from the Laravel auth config
$user_class = config('auth.providers.users.model');
// Verify the passed in state value
$this->user_flow->validateState($request->state);
// TODO: Check for error response
// Exchange the code for a token
$token = $this->user_flow->exchangeCodeForToken($request->code, config('authkit.authn.openid.redirect_uri'));
$user_info = $token->getUserInfo();
$oidc_token = $this->user_flow->exchangeCodeForToken($request->code, config('authkit.authn.openid.redirect_uri'));
$user_info = $oidc_token->getUserInfo();
// Try and use the token to find the local user
$user = \Auth::loginUsingId($token->getUserId());
// Try and find a token for that user id
$token = Token::where('id', $oidc_token->getUserId())->first();
// If that failed
if ($user === false)
if (!isset($token))
{
// User doesn't exist, create them.
$user = new $user_class();
$id_field = $user->getAuthIdentifierName();
$user->{$id_field} = $token->getUserId();
$user->name = $user_info['name'];
$user->email = $user_info['email'];
if ($user instanceof \authkit2\Models\IAuthkitUser)
{
$user->{$user->getAccessTokenName()} = $token->getAccessToken();
$user->{$user->getRefreshTokenName()} = $token->getRefreshToken();
}
else
// No token for that user, either create them or migrate
$register_event_result = event(new \authkit2\Events\UserRegistration($user_info));
$user = null;
foreach ($register_event_result as $result)
{
$user->authkit_access_token = $token->getAccessToken();
$user->authkit_refresh_token = $token->getRefreshToken();
}
$register_event_result = event(new \authkit2\Events\UserRegistration($user));
if (sizeof($register_event_result))
{
foreach ($register_event_result as $result)
if (is_object($result))
{
if ($result instanceof $user_class)
{
$user = $result;
}
$user = $result;
}
}
if (!$user->exists)
{
$user->save();
}
\Auth::login($user);
}
else
{
$user->name = $user_info['name'];
$user->email = $user_info['email'];
if ($user instanceof \authkit2\Models\IAuthkitUser)
{
$user->{$user->getAccessTokenName()} = $token->getAccessToken();
$user->{$user->getRefreshTokenName()} = $token->getRefreshToken();
}
else
if (!isset($user))
{
$user->authkit_access_token = $token->getAccessToken();
$user->authkit_refresh_token = $token->getRefreshToken();
// TODO: Log a useful error
abort(500);
}
$user->save();
$token = new Token();
$token->id = $oidc_token->getUserId();
$token->access_token = $oidc_token->getAccessToken();
$token->refresh_token = $oidc_token->getRefreshToken();
$token->user_id = $user->{$user->getAuthIdentifierName()};
$token->save();
}
event(new \authkit2\Events\UserLogin($user));
\Auth::login($user);
event(new \authkit2\Events\UserLogin($user, $user_info));
return redirect(url(config('authkit.authn.urls.post_login')));
}

9
src/Models/IAuthkitUser.php

@ -1,9 +0,0 @@
<?php
namespace authkit2\Models;
interface IAuthkitUser
{
public function getAccessTokenName(): string;
public function getRefreshTokenName(): string;
}

20
src/Models/Token.php

@ -0,0 +1,20 @@
<?php
namespace authkit2\Models;
use \Illuminate\Database\Eloquent\Model;
/**
* User's OIDC token
*
* @property string id
* @property int user_id
* @property string access_token
* @property string refresh_token
*/
class Token extends Model
{
protected $table = 'authkit2_token';
public $timestamps = false;
}

29
src/Models/User.php

@ -1,29 +0,0 @@
<?php
declare(strict_types=1);
namespace authkit2\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
/**
* Sample user model compatible with authkit
*/
class User extends Authenticatable
{
/** @var string[] */
protected $fillable = [
'name',
'email'
];
/** @var string[] */
protected $hidden = [
'remember_token'
];
public function getAuthIdentifierName()
{
return 'authkit_id';
}
}

43
src/Observers/UserObserver.php

@ -2,6 +2,7 @@
namespace authkit2\Observers;
use authkit2\Authkit2;
use authkit2\Models\Token;
class UserObserver
{
@ -13,49 +14,33 @@ class UserObserver
public function retrieved($user)
{
// Grab the token and refresh off of the user
if ($user instanceof \authkit2\Models\IAuthkitUser)
{
$token = $user->{$user->getAccessTokenName()};
$refresh = $user->{$user->getRefreshTokenName()};
}
else
{
$token = $user->authkit_access_token;
$refresh = $user->authkit_refresh_token;
}
// Find the token + refresh token for the user
$token = Token::where('user_id', $user->{$user->getAuthIdentifierName()})->first();
// Create a token object
$user->authkit = Authkit2::get_token($token, $refresh);
$user->authkit = Authkit2::get_token($token->access_token, $token->refresh_token);
// Set a refresh callback on the token -- when it's been refreshed,
// then set the new tokens on the user and save it
$user->authkit->setRefreshCallback(function($token) use ($user) {
if ($user instanceof \authkit2\Models\IAuthkitUser)
{
$user->{$user->getAccessTokenName()} = $token->getAccessToken();
$user->{$user->getRefreshTokenName()} = $token->getRefreshToken();
}
else
{
$user->authkit_access_token = $token->getAccessToken();
$user->authkit_refresh_token = $token->getRefreshToken();
}
$user->save();
// save the new tokens.
$user->authkit->setRefreshCallback(function($oidc_token) use ($token) {
$token->access_token = $oidc_token->getAccessToken();
$token->refresh_token = $oidc_token->getRefreshToken();
$token->save();
});
}
public function saving($user)
{
if (isset($user->authkit))
static::$token_cache[$user->authkit->getAccessToken()] = $user->authkit;
static::$token_cache[$user->{$user->getAuthIdentifierName()}] = $user->authkit;
unset($user->authkit);
}
public function saved($user)
{
$access_token = ($user instanceof \authkit2\Models\IAuthkitUser) ? $user->{$user->getAccessTokenName()} : $user->authkit_access_token;
if (isset(static::$token_cache[$access_token]))
$user->authkit = static::$token_cache[$access_token];
$user_id = $user->{$user->getAuthIdentifierName()};
if (isset(static::$token_cache[$user_id]))
$user->authkit = static::$token_cache[$user_id];
}
}

12
src/Providers/Authkit2ServiceProvider.php

@ -39,14 +39,16 @@ class Authkit2ServiceProvider extends ServiceProvider
{
$this->publishes([
__DIR__.'/../../config/authkit.php' => config_path('authkit.php')
], 'config');
], 'authkit2_config');
$this->publishes([
__DIR__.'/../../database/migrations/new/authkit2_users_update.php' => database_path('migrations/'.date('Y_m_d_His').'_authkit2_users_update.php')
], 'migrations_new');
__DIR__.'/../../database/migrations/authkit2_token.php' => database_path('migrations/'.date('Y_m_d_His').'_authkit2_token.php'),
__DIR__.'/../../database/migrations/new/authkit2_users_update.php' => database_path('migrations/'.date('Y_m_d_His').'_authkit2_fresh_project.php')
], 'authkit2_migrate_fresh_project');
$this->publishes([
__DIR__.'/../../database/migrations/existing/authkit2_users_update_minimal.php' => database_path('migrations/'.date('Y_m_d_His').'_authkit2_users_update_minimal.php')
], 'migrations_existing');
__DIR__.'/../../database/migrations/authkit2_token.php' => database_path('migrations/'.date('Y_m_d_His').'_authkit2_token.php'),
__DIR__.'/../../database/migrations/existing/authkit2_users_update_minimal.php' => database_path('migrations/'.date('Y_m_d_His').'_authkit2_existing_project.php')
], 'authkit2_migrate_existing_project');
}
}
}

11
src/Providers/AuthnServiceProvider.php

@ -120,8 +120,13 @@ class AuthnServiceProvider extends ServiceProvider
]);
}
$this->app->booted(function($app) {
Authkit2::configure(config('authkit.authn.openid.client_id'), config('authkit.authn.openid.client_secret'), config('authkit.authn.openid.endpoint'));
});
// Don't even try and init authkit unless configuration is present, prevents erroring out when
// running a composer require/composer install without credentials present.
if (config('authkit.authn.openid.client_id') != null && config('authkit.authn.openid.client_secret') != null && config('authkit.authn.openid.endpoint') != null)
{
$this->app->booted(function($app) {
Authkit2::configure(config('authkit.authn.openid.client_id'), config('authkit.authn.openid.client_secret'), config('authkit.authn.openid.endpoint'));
});
}
}
}

Loading…
Cancel
Save