Fix PasskeysController with new webauthn library version
Some checks failed
PHP Unit / PHPUnit test suite (pull_request) Has been cancelled
Laravel Pint / Laravel Pint (pull_request) Has been cancelled

This commit is contained in:
Jonny Barnes 2025-04-07 19:44:13 +01:00
parent 126bb29ae2
commit cf978cd749
Signed by: jonny
SSH key fingerprint: SHA256:CTuSlns5U7qlD9jqHvtnVmfYV3Zwl2Z7WnJ4/dqOaL8
2 changed files with 68 additions and 40 deletions

View file

@ -4,8 +4,6 @@ APP_KEY=
APP_DEBUG=true APP_DEBUG=true
APP_TIMEZONE=UTC APP_TIMEZONE=UTC
APP_URL=https://example.com APP_URL=https://example.com
APP_LONGURL=example.com
APP_SHORTURL=examp.le
APP_LOCALE=en APP_LOCALE=en
APP_FALLBACK_LOCALE=en APP_FALLBACK_LOCALE=en

View file

@ -18,6 +18,7 @@ use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\View\View; use Illuminate\View\View;
use ParagonIE\ConstantTime\Base64UrlSafe; use ParagonIE\ConstantTime\Base64UrlSafe;
use Random\RandomException;
use Throwable; use Throwable;
use Webauthn\AttestationStatement\AttestationStatementSupportManager; use Webauthn\AttestationStatement\AttestationStatementSupportManager;
use Webauthn\AttestationStatement\NoneAttestationStatementSupport; use Webauthn\AttestationStatement\NoneAttestationStatementSupport;
@ -52,22 +53,26 @@ class PasskeysController extends Controller
return view('admin.passkeys.index', compact('passkeys')); return view('admin.passkeys.index', compact('passkeys'));
} }
public function getCreateOptions(): JsonResponse /**
* @throws RandomException
* @throws \JsonException
*/
public function getCreateOptions(Request $request): JsonResponse
{ {
/** @var User $user */ /** @var User $user */
$user = auth()->user(); $user = auth()->user();
// RP Entity i.e. the application // RP Entity i.e. the application
$rpEntity = PublicKeyCredentialRpEntity::create( $rpEntity = PublicKeyCredentialRpEntity::create(
config('app.name'), name: config('app.name'),
config('app.url'), id: config('app.url'),
); );
// User Entity // User Entity
$userEntity = PublicKeyCredentialUserEntity::create( $userEntity = PublicKeyCredentialUserEntity::create(
$user->name, name: $user->name,
(string) $user->id, id: (string) $user->id,
$user->name, displayName: $user->name,
); );
// Challenge // Challenge
@ -85,25 +90,38 @@ class PasskeysController extends Controller
$authenticatorSelectionCriteria = AuthenticatorSelectionCriteria::create( $authenticatorSelectionCriteria = AuthenticatorSelectionCriteria::create(
userVerification: AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_REQUIRED, userVerification: AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_REQUIRED,
residentKey: AuthenticatorSelectionCriteria::RESIDENT_KEY_REQUIREMENT_REQUIRED, residentKey: AuthenticatorSelectionCriteria::RESIDENT_KEY_REQUIREMENT_REQUIRED,
requireResidentKey: true,
); );
$options = PublicKeyCredentialCreationOptions::create( $publicKeyCredentialCreationOptions = PublicKeyCredentialCreationOptions::create(
$rpEntity, rp: $rpEntity,
$userEntity, user: $userEntity,
$challenge, challenge: $challenge,
$pubKeyCredParams, pubKeyCredParams: $pubKeyCredParams,
authenticatorSelection: $authenticatorSelectionCriteria, authenticatorSelection: $authenticatorSelectionCriteria,
attestation: PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE attestation: PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE
); );
$options = json_encode($options, JSON_THROW_ON_ERROR); $attestationStatementSupportManager = new AttestationStatementSupportManager;
$attestationStatementSupportManager->add(new NoneAttestationStatementSupport);
$webauthnSerializerFactory = new WebauthnSerializerFactory(
attestationStatementSupportManager: $attestationStatementSupportManager
);
$webauthnSerializer = $webauthnSerializerFactory->create();
$publicKeyCredentialCreationOptions = $webauthnSerializer->serialize(
data: $publicKeyCredentialCreationOptions,
format: 'json'
);
session(['create_options' => $options]); $request->session()->put('create_options', $publicKeyCredentialCreationOptions);
return JsonResponse::fromJsonString($options); return JsonResponse::fromJsonString($publicKeyCredentialCreationOptions);
} }
/**
* @throws Throwable
* @throws WebauthnException
* @throws \JsonException
*/
public function create(Request $request): JsonResponse public function create(Request $request): JsonResponse
{ {
/** @var User $user */ /** @var User $user */
@ -111,17 +129,17 @@ class PasskeysController extends Controller
$publicKeyCredentialCreationOptionsData = session('create_options'); $publicKeyCredentialCreationOptionsData = session('create_options');
// Unset session data to mitigate replay attacks // Unset session data to mitigate replay attacks
session()->forget('create_options'); $request->session()->forget('create_options');
if (empty($publicKeyCredentialCreationOptionsData)) { if (empty($publicKeyCredentialCreationOptionsData)) {
throw new WebAuthnException('No public key credential request options found'); throw new WebAuthnException('No public key credential request options found');
} }
$attestationStatementSupportManager = new AttestationStatementSupportManager; $attestationStatementSupportManager = new AttestationStatementSupportManager;
$attestationStatementSupportManager->add(new NoneAttestationStatementSupport); $attestationStatementSupportManager->add(new NoneAttestationStatementSupport);
$webauthnSerializerFactory = new WebauthnSerializerFactory(
$webauthnSerializer = (new WebauthnSerializerFactory( attestationStatementSupportManager: $attestationStatementSupportManager
$attestationStatementSupportManager );
))->create(); $webauthnSerializer = $webauthnSerializerFactory->create();
$publicKeyCredential = $webauthnSerializer->deserialize( $publicKeyCredential = $webauthnSerializer->deserialize(
json_encode($request->all(), JSON_THROW_ON_ERROR), json_encode($request->all(), JSON_THROW_ON_ERROR),
@ -146,11 +164,11 @@ class PasskeysController extends Controller
$ceremonyStepManagerFactory->setExtensionOutputCheckerHandler( $ceremonyStepManagerFactory->setExtensionOutputCheckerHandler(
ExtensionOutputCheckerHandler::create() ExtensionOutputCheckerHandler::create()
); );
$securedRelyingPartyId = []; $allowedOrigins = [];
if (App::environment('local', 'development')) { if (App::environment('local', 'development')) {
$securedRelyingPartyId = [config('app.url')]; $allowedOrigins = [config('app.url')];
} }
$ceremonyStepManagerFactory->setSecuredRelyingPartyId($securedRelyingPartyId); $ceremonyStepManagerFactory->setAllowedOrigins($allowedOrigins);
$authenticatorAttestationResponseValidator = AuthenticatorAttestationResponseValidator::create( $authenticatorAttestationResponseValidator = AuthenticatorAttestationResponseValidator::create(
ceremonyStepManager: $ceremonyStepManagerFactory->creationCeremony() ceremonyStepManager: $ceremonyStepManagerFactory->creationCeremony()
@ -165,8 +183,7 @@ class PasskeysController extends Controller
$publicKeyCredentialSource = $authenticatorAttestationResponseValidator->check( $publicKeyCredentialSource = $authenticatorAttestationResponseValidator->check(
authenticatorAttestationResponse: $publicKeyCredential->response, authenticatorAttestationResponse: $publicKeyCredential->response,
publicKeyCredentialCreationOptions: $publicKeyCredentialCreationOptions, publicKeyCredentialCreationOptions: $publicKeyCredentialCreationOptions,
request: config('app.url'), host: config('app.url')
securedRelyingPartyId: $securedRelyingPartyId,
); );
$user->passkey()->create([ $user->passkey()->create([
@ -180,24 +197,37 @@ class PasskeysController extends Controller
]); ]);
} }
public function getRequestOptions(): JsonResponse /**
* @throws RandomException
* @throws \JsonException
*/
public function getRequestOptions(Request $request): JsonResponse
{ {
$publicKeyCredentialRequestOptions = PublicKeyCredentialRequestOptions::create( $publicKeyCredentialRequestOptions = PublicKeyCredentialRequestOptions::create(
challenge: random_bytes(16), challenge: random_bytes(16),
userVerification: PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_REQUIRED userVerification: PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_REQUIRED
); );
$publicKeyCredentialRequestOptions = json_encode($publicKeyCredentialRequestOptions, JSON_THROW_ON_ERROR); $attestationStatementSupportManager = AttestationStatementSupportManager::create();
$attestationStatementSupportManager->add(NoneAttestationStatementSupport::create());
$factory = new WebauthnSerializerFactory(
attestationStatementSupportManager: $attestationStatementSupportManager
);
$serializer = $factory->create();
$publicKeyCredentialRequestOptions = $serializer->serialize(data: $publicKeyCredentialRequestOptions, format: 'json');
session(['request_options' => $publicKeyCredentialRequestOptions]); $request->session()->put('request_options', $publicKeyCredentialRequestOptions);
return JsonResponse::fromJsonString($publicKeyCredentialRequestOptions); return JsonResponse::fromJsonString($publicKeyCredentialRequestOptions);
} }
/**
* @throws \JsonException
*/
public function login(Request $request): JsonResponse public function login(Request $request): JsonResponse
{ {
$requestOptions = session('request_options'); $requestOptions = session('request_options');
session()->forget('request_options'); $request->session()->forget('request_options');
if (empty($requestOptions)) { if (empty($requestOptions)) {
return response()->json([ return response()->json([
@ -209,9 +239,10 @@ class PasskeysController extends Controller
$attestationStatementSupportManager = new AttestationStatementSupportManager; $attestationStatementSupportManager = new AttestationStatementSupportManager;
$attestationStatementSupportManager->add(new NoneAttestationStatementSupport); $attestationStatementSupportManager->add(new NoneAttestationStatementSupport);
$webauthnSerializer = (new WebauthnSerializerFactory( $webauthnSerializerFactory = new WebauthnSerializerFactory(
$attestationStatementSupportManager attestationStatementSupportManager: $attestationStatementSupportManager
))->create(); );
$webauthnSerializer = $webauthnSerializerFactory->create();
$publicKeyCredential = $webauthnSerializer->deserialize( $publicKeyCredential = $webauthnSerializer->deserialize(
json_encode($request->all(), JSON_THROW_ON_ERROR), json_encode($request->all(), JSON_THROW_ON_ERROR),
@ -256,11 +287,11 @@ class PasskeysController extends Controller
$ceremonyStepManagerFactory->setExtensionOutputCheckerHandler( $ceremonyStepManagerFactory->setExtensionOutputCheckerHandler(
ExtensionOutputCheckerHandler::create() ExtensionOutputCheckerHandler::create()
); );
$securedRelyingPartyId = []; $allowedOrigins = [];
if (App::environment('local', 'development')) { if (App::environment('local', 'development')) {
$securedRelyingPartyId = [config('app.url')]; $allowedOrigins = [config('app.url')];
} }
$ceremonyStepManagerFactory->setSecuredRelyingPartyId($securedRelyingPartyId); $ceremonyStepManagerFactory->setAllowedOrigins($allowedOrigins);
$authenticatorAssertionResponseValidator = AuthenticatorAssertionResponseValidator::create( $authenticatorAssertionResponseValidator = AuthenticatorAssertionResponseValidator::create(
ceremonyStepManager: $ceremonyStepManagerFactory->requestCeremony() ceremonyStepManager: $ceremonyStepManagerFactory->requestCeremony()
@ -274,12 +305,11 @@ class PasskeysController extends Controller
try { try {
$authenticatorAssertionResponseValidator->check( $authenticatorAssertionResponseValidator->check(
credentialId: $publicKeyCredentialSource, publicKeyCredentialSource: $publicKeyCredentialSource,
authenticatorAssertionResponse: $publicKeyCredential->response, authenticatorAssertionResponse: $publicKeyCredential->response,
publicKeyCredentialRequestOptions: $publicKeyCredentialRequestOptions, publicKeyCredentialRequestOptions: $publicKeyCredentialRequestOptions,
request: config('app.url'), host: config('app.url'),
userHandle: null, userHandle: null,
securedRelyingPartyId: $securedRelyingPartyId,
); );
} catch (Throwable) { } catch (Throwable) {
return response()->json([ return response()->json([