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\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
class Authkit2UsersUpdateMinimal extends Migration class Authkit2ExistingProject extends Migration
{ {
/** /**
* Run the migrations. * Run the migrations.
@ -15,16 +15,9 @@ class Authkit2UsersUpdateMinimal extends Migration
*/ */
public function up() 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) { Schema::table('users', static function(Blueprint $table) {
$table->string('password')->nullable()->change(); $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() public function down()
{ {
Schema::table('users', static function(Blueprint $table) { 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(); $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\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
class Authkit2UsersUpdate extends Migration class Authkit2FreshProject extends Migration
{ {
/** /**
* Run the migrations. * Run the migrations.
@ -15,16 +15,9 @@ class Authkit2UsersUpdate extends Migration
*/ */
public function up() 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) { Schema::table('users', static function(Blueprint $table) {
$table->dropColumn(['email_verified_at', 'password']); $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() public function down()
{ {
Schema::table('users', static function(Blueprint $table) { Schema::table('users', static function(Blueprint $table) {
$table->dropColumn(['authkit_id', 'authkit_access_token', 'authkit_refresh_token']);
$table->timestamp('email_verified_at')->nullable(); $table->timestamp('email_verified_at')->nullable();
$table->string('password'); $table->string('password');
}); });

18
src/Events/UserLogin.php

@ -9,4 +9,22 @@ namespace authkit2\Events;
*/ */
class UserLogin extends UserEvent 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; namespace authkit2\Http\Controllers;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use authkit2\Models\Token;
/** /**
* Methods for handling user authentication operations * Methods for handling user authentication operations
@ -43,75 +44,49 @@ class AuthenticationController extends Controller
*/ */
public function callback(Request $request) 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 // Verify the passed in state value
$this->user_flow->validateState($request->state); $this->user_flow->validateState($request->state);
// TODO: Check for error response // TODO: Check for error response
// Exchange the code for a token // Exchange the code for a token
$token = $this->user_flow->exchangeCodeForToken($request->code, config('authkit.authn.openid.redirect_uri')); $oidc_token = $this->user_flow->exchangeCodeForToken($request->code, config('authkit.authn.openid.redirect_uri'));
$user_info = $token->getUserInfo(); $user_info = $oidc_token->getUserInfo();
// Try and use the token to find the local user // Try and find a token for that user id
$user = \Auth::loginUsingId($token->getUserId()); $token = Token::where('id', $oidc_token->getUserId())->first();
// If that failed // If that failed
if ($user === false) if (!isset($token))
{ {
// User doesn't exist, create them. // No token for that user, either create them or migrate
$user = new $user_class(); $register_event_result = event(new \authkit2\Events\UserRegistration($user_info));
$id_field = $user->getAuthIdentifierName(); $user = null;
$user->{$id_field} = $token->getUserId(); foreach ($register_event_result as $result)
$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
{ {
$user->authkit_access_token = $token->getAccessToken(); if (is_object($result))
$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 ($result instanceof $user_class) $user = $result;
{
$user = $result;
}
} }
} }
if (!$user->exists)
{ if (!isset($user))
$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
{ {
$user->authkit_access_token = $token->getAccessToken(); // TODO: Log a useful error
$user->authkit_refresh_token = $token->getRefreshToken(); 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'))); 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; namespace authkit2\Observers;
use authkit2\Authkit2; use authkit2\Authkit2;
use authkit2\Models\Token;
class UserObserver class UserObserver
{ {
@ -13,49 +14,33 @@ class UserObserver
public function retrieved($user) public function retrieved($user)
{ {
// Grab the token and refresh off of the user // Find the token + refresh token for the user
if ($user instanceof \authkit2\Models\IAuthkitUser) $token = Token::where('user_id', $user->{$user->getAuthIdentifierName()})->first();
{
$token = $user->{$user->getAccessTokenName()};
$refresh = $user->{$user->getRefreshTokenName()};
}
else
{
$token = $user->authkit_access_token;
$refresh = $user->authkit_refresh_token;
}
// Create a token object // 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, // Set a refresh callback on the token -- when it's been refreshed,
// then set the new tokens on the user and save it // save the new tokens.
$user->authkit->setRefreshCallback(function($token) use ($user) { $user->authkit->setRefreshCallback(function($oidc_token) use ($token) {
if ($user instanceof \authkit2\Models\IAuthkitUser) $token->access_token = $oidc_token->getAccessToken();
{ $token->refresh_token = $oidc_token->getRefreshToken();
$user->{$user->getAccessTokenName()} = $token->getAccessToken(); $token->save();
$user->{$user->getRefreshTokenName()} = $token->getRefreshToken();
}
else
{
$user->authkit_access_token = $token->getAccessToken();
$user->authkit_refresh_token = $token->getRefreshToken();
}
$user->save();
}); });
} }
public function saving($user) public function saving($user)
{ {
if (isset($user->authkit)) if (isset($user->authkit))
static::$token_cache[$user->authkit->getAccessToken()] = $user->authkit; static::$token_cache[$user->{$user->getAuthIdentifierName()}] = $user->authkit;
unset($user->authkit); unset($user->authkit);
} }
public function saved($user) public function saved($user)
{ {
$access_token = ($user instanceof \authkit2\Models\IAuthkitUser) ? $user->{$user->getAccessTokenName()} : $user->authkit_access_token; $user_id = $user->{$user->getAuthIdentifierName()};
if (isset(static::$token_cache[$access_token])) if (isset(static::$token_cache[$user_id]))
$user->authkit = static::$token_cache[$access_token]; $user->authkit = static::$token_cache[$user_id];
} }
} }

12
src/Providers/Authkit2ServiceProvider.php

@ -39,14 +39,16 @@ class Authkit2ServiceProvider extends ServiceProvider
{ {
$this->publishes([ $this->publishes([
__DIR__.'/../../config/authkit.php' => config_path('authkit.php') __DIR__.'/../../config/authkit.php' => config_path('authkit.php')
], 'config'); ], 'authkit2_config');
$this->publishes([ $this->publishes([
__DIR__.'/../../database/migrations/new/authkit2_users_update.php' => database_path('migrations/'.date('Y_m_d_His').'_authkit2_users_update.php') __DIR__.'/../../database/migrations/authkit2_token.php' => database_path('migrations/'.date('Y_m_d_His').'_authkit2_token.php'),
], 'migrations_new'); __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([ $this->publishes([
__DIR__.'/../../database/migrations/existing/authkit2_users_update_minimal.php' => database_path('migrations/'.date('Y_m_d_His').'_authkit2_users_update_minimal.php') __DIR__.'/../../database/migrations/authkit2_token.php' => database_path('migrations/'.date('Y_m_d_His').'_authkit2_token.php'),
], 'migrations_existing'); __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) { // Don't even try and init authkit unless configuration is present, prevents erroring out when
Authkit2::configure(config('authkit.authn.openid.client_id'), config('authkit.authn.openid.client_secret'), config('authkit.authn.openid.endpoint')); // 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