Reputation: 263
I am trying to now authenticate my users, but it seems like web-auth/webauthn-lib
(v5) is now expecting a userHandle
even though when it has been set to null?
{"error":"Invalid user handle"}
This is odd because i am setting:
$publicKeyCredentialSource = AuthenticatorAssertionResponseValidator::create(
(new CeremonyStepManagerFactory())->requestCeremony()
)->check(
publicKeyCredentialSource: $credentialSource,
authenticatorAssertionResponse: $publicKeyCredential->response,
publicKeyCredentialRequestOptions: $requestOptions,
host: $request->getHost(),
userHandle: null,
);
Although, in the database I can see the source, which has the following:
"userHandle": "TVE",
This, if we decode it, it will return it to a base64: "MQ"
, which, if decoded, translates to 1
.
This is confusing, and I am not sure why its checking the user handle if I am setting it to null
I tried adding $request->user()->id
but that didn't work either. Even hardcoding TVE
or MQ
won't work. It shows the same error:
{"error":"Invalid user handle"}
In my registration, I am doing the following (in case this helps):
public function registerOptions(Request $request) {
$userId = $request->user()->id;
$challenge = Str::random();
// Encode user id and challenge in base64url
// $encodedUserId = Base64Url::encode($userId);
// $encodedChallenge = Base64Url::encode($challenge);
$options = PublicKeyCredentialCreationOptions::create(
rp: new PublicKeyCredentialRpEntity(
name: 'Authen',
id: parse_url(config('app.url'), PHP_URL_HOST),
icon: null
),
user: new PublicKeyCredentialUserEntity(
name: $request->user()->email,
id: $request->user()->id,
displayName: $request->user()->name,
),
challenge: Str::random(),
authenticatorSelection: new AuthenticatorSelectionCriteria(
// authenticatorAttachment: AuthenticatorSelectionCriteria::AUTHENTICATOR_ATTACHMENT_NO_PREFERENCE,
),
);
return JsonSerializer::serialize($options);
}
Finally, when I store it:
public function store(Request $request) {
$data = $request->validate([
'passkey' => ['required', 'json'],
'device_name' => ['required', 'string', 'max:255'],
'options' => ['required'],
]);
/** @var PublicKeyCredential $publicKeyCredential */
$publicKeyCredential = (new WebauthnSerializerFactory(AttestationStatementSupportManager::create()))
->create()
->deserialize($data['passkey'], PublicKeyCredential::class, 'json');
$optionsData = json_decode($data['options']);
$rp = new PublicKeyCredentialRpEntity(
$optionsData->rp->name,
$optionsData->rp->id,
$optionsData->rp->icon ?? null // Optional parameter
);
$user = new PublicKeyCredentialUserEntity(
$optionsData->user->name,
$optionsData->user->id,
$optionsData->user->displayName,
null
);
$authenticatorSelection = new AuthenticatorSelectionCriteria(
$optionsData->authenticatorSelection->authenticatorAttachment,
$optionsData->authenticatorSelection->userVerification,
$optionsData->authenticatorSelection->residentKey,
);
$options = new PublicKeyCredentialCreationOptions(
$rp,
$user,
Base64Url::decode($optionsData->challenge),
$optionsData->pubKeyCredParams,
$authenticatorSelection,
null, // Timeout (optional, can be null)
$optionsData->excludeCredentials,
null, // Attestation (optional, can be null)
null // Extensions (optional, can be null)
);
if(!$publicKeyCredential->response instanceof AuthenticatorAttestationResponse) {
return response()->json(['error' => 'invalid response'], 400);
}
try {
$publicKeyCredentialSource = AuthenticatorAttestationResponseValidator::create(
(new CeremonyStepManagerFactory())->creationCeremony(),
)->check(
authenticatorAttestationResponse: $publicKeyCredential->response,
publicKeyCredentialCreationOptions: $options,
host: $request->getHost(),
);
$agent = new Agent();
$device = $request->user()->devices()->create([
'device_name' => $data['device_name'],
'device_type' => $agent->deviceType(),
]);
$passkey = $device->passkey()->create([
'user_id' => $request->user()->id,
'device_id' => $device->id,
'credential_id' => $publicKeyCredentialSource->publicKeyCredentialId,
'public_key_credential_source' => JsonSerializer::serialize($publicKeyCredentialSource), // Triggers the mutator
'device_type' => $agent->deviceType(),
]);
// Update the device to associate the passkey
$device->update(['passkey_id' => $passkey->id]);
} catch (\Exception $exception){
return response()->json(['error' => $exception->getMessage()], 400);
}
return response()->json(['message' => 'success'], 200);
}
Cheers.
Upvotes: 1
Views: 94