Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
joetannenbaum committed Jan 13, 2023
1 parent 3409a95 commit 016b695
Show file tree
Hide file tree
Showing 9 changed files with 130 additions and 69 deletions.
14 changes: 0 additions & 14 deletions app/Auth/CredentialSourceRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,36 +12,22 @@ class CredentialSourceRepository implements PublicKeyCredentialSourceRepository
{
public function findOneByCredentialId(string $publicKeyCredentialId): ?PublicKeyCredentialSource
{
ray('findOneByCredentialId');
ray($publicKeyCredentialId);
ray(base64_encode($publicKeyCredentialId));

$authenticator = Authenticator::where('credential_id', base64_encode($publicKeyCredentialId))->first();

if (!$authenticator) {
return null;
}

ray($authenticator);

return PublicKeyCredentialSource::createFromArray($authenticator->public_key);
}

public function findAllForUserEntity(PublicKeyCredentialUserEntity $publicKeyCredentialUserEntity): array
{
ray('findAllForUserEntity');
ray($publicKeyCredentialUserEntity);
ray($publicKeyCredentialUserEntity->getName());
ray($publicKeyCredentialUserEntity->getId());

return User::with('authenticators')->where('id', $publicKeyCredentialUserEntity->getId())->first()->authenticators->toArray();
}

public function saveCredentialSource(PublicKeyCredentialSource $publicKeyCredentialSource): void
{
ray('saveCredentialSource');
ray($publicKeyCredentialSource);

$user = User::where('id', $publicKeyCredentialSource->getUserHandle())->firstOrFail();

$user->authenticators()->save(new Authenticator([
Expand Down
27 changes: 22 additions & 5 deletions app/Http/Controllers/AuthenticationController.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@
use Cose\Algorithm\Signature\RSA\RS256;
use Cose\Algorithm\Signature\RSA\RS384;
use Cose\Algorithm\Signature\RSA\RS512;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\ValidationException;
use Psr\Http\Message\ServerRequestInterface;
use Webauthn\AttestationStatement\AttestationObjectLoader;
use Webauthn\AttestationStatement\AttestationStatementSupportManager;
Expand All @@ -36,12 +39,18 @@ class AuthenticationController extends Controller
{
public function generateOptions(Request $request)
{
$user = User::where('email', $request->input('username'))->firstOrFail();
try {
$user = User::where('username', $request->input('username'))->firstOrFail();
} catch (ModelNotFoundException $e) {
throw ValidationException::withMessages([
'username' => 'User not found',
]);
}

$userEntity = PublicKeyCredentialUserEntity::create(
$user->email,
$user->username,
(string) $user->id,
$user->name,
$user->username,
null,
);

Expand Down Expand Up @@ -117,7 +126,9 @@ public function verify(Request $request, ServerRequestInterface $serverRequest)
$authenticatorAssertionResponse = $publicKeyCredential->getResponse();

if (!$authenticatorAssertionResponse instanceof AuthenticatorAssertionResponse) {
abort(403, 'Invalid response type');
throw ValidationException::withMessages([
'username' => 'Invalid response type',
]);
}

$publicKeyCredentialSource = $authenticatorAttestationResponseValidator->check(
Expand All @@ -130,6 +141,12 @@ public function verify(Request $request, ServerRequestInterface $serverRequest)

$request->session()->forget('publicKeyCredentialRequestOptions');

ray('log user in now!!!!');
$user = User::where('id', $publicKeyCredentialSource->getUserHandle())->firstOrFail();

Auth::login($user);

return [
'verified' => true,
];
}
}
43 changes: 33 additions & 10 deletions app/Http/Controllers/RegistrationController.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
use App\Auth\CredentialSourceRepository;
use App\Models\User;
use Cose\Algorithms;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use Psr\Http\Message\ServerRequestInterface;
use Webauthn\AttestationStatement\AttestationObjectLoader;
use Webauthn\AttestationStatement\AttestationStatementSupportManager;
Expand All @@ -24,6 +27,8 @@

class RegistrationController extends Controller
{
const CREDENTIAL_CREATION_OPTIONS_SESSION_KEY = 'publicKeyCredentialCreationOptions';

public function generateOptions(Request $request)
{
$rpEntity = PublicKeyCredentialRpEntity::create(
Expand All @@ -33,19 +38,17 @@ public function generateOptions(Request $request)
);

$user = User::firstOrNew([
'email' => $request->input('username'),
'username' => $request->input('username'),
]);

if (!$user->exists) {
$user->name = $request->input('username');
$user->password = Str::random(32);
$user->save();
}

$userEntity = PublicKeyCredentialUserEntity::create(
$user->email,
$user->username,
$user->id,
$user->name,
$user->username,
null,
);

Expand Down Expand Up @@ -94,7 +97,10 @@ public function generateOptions(Request $request)
$serializedPublicKeyCredentialCreationOptions['excludeCredentials'] = [];
}

$request->session()->put('publicKeyCredentialCreationOptions', $serializedPublicKeyCredentialCreationOptions);
$request->session()->put(
self::CREDENTIAL_CREATION_OPTIONS_SESSION_KEY,
$serializedPublicKeyCredentialCreationOptions
);

return $serializedPublicKeyCredentialCreationOptions;
}
Expand Down Expand Up @@ -130,17 +136,34 @@ public function verify(Request $request, ServerRequestInterface $serverRequest)
$authenticatorAttestationResponse = $publicKeyCredential->getResponse();

if (!$authenticatorAttestationResponse instanceof AuthenticatorAttestationResponse) {
abort(403, 'Invalid response type');
throw ValidationException::withMessages([
'username' => 'Invalid response type',
]);
}

$publicKeyCredentialSource = $authenticatorAttestationResponseValidator->check(
$authenticatorAttestationResponse,
PublicKeyCredentialCreationOptions::createFromArray(session('publicKeyCredentialCreationOptions')),
PublicKeyCredentialCreationOptions::createFromArray(
session(self::CREDENTIAL_CREATION_OPTIONS_SESSION_KEY)
),
$serverRequest
);

$request->session()->forget('publicKeyCredentialCreationOptions');
$request->session()->forget(self::CREDENTIAL_CREATION_OPTIONS_SESSION_KEY);

try {
$publicKeyCredentialSourceRepository->saveCredentialSource($publicKeyCredentialSource);
$user = User::where('id', $publicKeyCredentialSource->getUserHandle())->firstOrFail();
} catch (ModelNotFoundException $e) {
throw ValidationException::withMessages([
'username' => 'User not found',
]);
}

Auth::login($user);

$publicKeyCredentialSourceRepository->saveCredentialSource($publicKeyCredentialSource);
return [
'verified' => true,
];
}
}
14 changes: 1 addition & 13 deletions app/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@ class User extends Authenticatable
* @var array<int, string>
*/
protected $fillable = [
'name',
'email',
'password',
'username',
];

/**
Expand All @@ -29,19 +27,9 @@ class User extends Authenticatable
* @var array<int, string>
*/
protected $hidden = [
'password',
'remember_token',
];

/**
* The attributes that should be cast.
*
* @var array<string, string>
*/
protected $casts = [
'email_verified_at' => 'datetime',
];

public function authenticators()
{
return $this->hasMany(Authenticator::class);
Expand Down
5 changes: 1 addition & 4 deletions database/migrations/2014_10_12_000000_create_users_table.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,7 @@ public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->string('username')->unique();
$table->rememberToken();
$table->timestamps();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,6 @@ public function up()
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->text('credential_id');
$table->text('public_key');
// $table->binary('public_key');
// $table->unsignedBigInteger('counter');
// $table->string('device_type');
// $table->boolean('backed_up');
// $table->string('transports');
$table->timestamps();
});
}
Expand Down
30 changes: 12 additions & 18 deletions resources/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,45 +26,39 @@ document.addEventListener('alpine:init', () => {
.post('/registration/options', {
username: this.username,
})
// Pass the options to the authenticator and wait for a response
.then((response) => startRegistration(response.data))
.then((attResp) => axios.post('/registration/verify', attResp))
.then((verificationResponse) => {
if (
verificationResponse.data &&
verificationResponse.data.verified
) {
console.log('success!');
} else {
console.log('invalid?', verificationResponse.data);
if (verificationResponse.data?.verified) {
return window.location.reload();
}

this.error =
'Something went wrong verifying the registration.';
})
.catch((error) => {
this.error = error;
this.error = error?.response?.data?.message || error;
});
},
submitLogin() {
window.axios
.post('/authentication/options', {
username: this.username,
})
// Pass the options to the authenticator and wait for a response
.then((response) => startAuthentication(response.data))
.then((attResp) =>
axios.post('/authentication/verify', attResp),
)
.then((verificationResponse) => {
if (
verificationResponse.data &&
verificationResponse.data.verified
) {
console.log('success!');
} else {
console.log('invalid?', verificationResponse.data);
if (verificationResponse.data?.verified) {
return window.location.reload();
}

this.error =
'Something went wrong verifying the authentication.';
})
.catch((error) => {
this.error = error;
this.error = error?.response?.data?.message || error;
});
},
}));
Expand Down
48 changes: 48 additions & 0 deletions resources/views/authed.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" class="h-full bg-gray-50">

<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">

<title>Laravel Passkey Authentication Demo | Joe Codes</title>

<link href="https://fonts.bunny.net/css2?family=Manrope:wght@400;500;600;700&display=swap" rel="stylesheet">

@vite(['resources/css/app.css', 'resources/js/app.js'])
<script defer src="https://unpkg.com/[email protected]/dist/cdn.min.js"></script>
</head>

<body class="h-full">

<div class="flex flex-col justify-center min-h-full px-4 py-12 sm:px-6 lg:px-8">
<div class="sm:mx-auto sm:w-full sm:max-w-md">
<svg xmlns="http://www.w3.org/2000/svg" class="w-auto h-12 mx-auto" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round"
d="M16.5 10.5V6.75a4.5 4.5 0 10-9 0v3.75m-.75 11.25h10.5a2.25 2.25 0 002.25-2.25v-6.75a2.25 2.25 0 00-2.25-2.25H6.75a2.25 2.25 0 00-2.25 2.25v6.75a2.25 2.25 0 002.25 2.25z" />
</svg>
<h2 class="mt-6 text-3xl font-bold tracking-tight text-center text-gray-900">Laravel Passkeys Demo</h2>
<p class="mt-2 text-sm text-center text-gray-600">
Something here, probably a link to the post and a note that the database deletes info every 30 minutes
</p>
</div>

<div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
<div class="px-4 py-8 space-y-4 text-center bg-white shadow sm:rounded-lg sm:px-10">
<p class="text-xl">👋 Well hey there, <strong>{{ $user->username }}</strong>!</p>
<div>
<form action="/logout" method="POST">
@csrf
<button type="submit"
class="inline-flex items-center px-3 py-2 text-sm font-medium leading-4 text-white border border-transparent rounded-md shadow-sm bg-sky-600 hover:bg-sky-700 focus:outline-none focus:ring-2 focus:ring-sky-500 focus:ring-offset-2">
Log out
</button>
</form>
</div>
</div>
</div>
</div>
</body>

</html>
13 changes: 13 additions & 0 deletions routes/web.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,25 @@

use App\Http\Controllers\AuthenticationController;
use App\Http\Controllers\RegistrationController;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Route;

Route::get('/', function () {
if (Auth::check()) {
return view('authed', [
'user' => Auth::user(),
]);
}

return view('home');
});

Route::post('logout', function () {
Auth::logout();

return redirect('/');
});

Route::prefix('registration')->controller(RegistrationController::class)->group(function () {
Route::post('/options', 'generateOptions');
Route::post('/verify', 'verify');
Expand Down

0 comments on commit 016b695

Please sign in to comment.