1// https://fusionauth.io/docs/lifecycle/authenticate-users/passwordless/webauthn-passkeys 2class FusionAuthWebAuthn 3{ 4 extraPayloadData = {}; 5 6 async startRegistration() { 7 const response = await this.executeHttpRequest('createPasskeyStart'); 8 const requestCredentialOptions = { 9 publicKey : { 10 ...response.data.options, 11 challenge : this.base64UrlToBuffer(response.data.options.challenge), 12 excludeCredentials : response.data.options.excludeCredentials?.map(credential => { 13 return { 14 ...credential, 15 id : this.base64UrlToBuffer(credential.id) 16 } 17 }) ?? [], 18 user : { 19 ...response.data.options.user, 20 id : this.base64UrlToBuffer(response.data.options.user.id) 21 } 22 } 23 }; 24 25 // Remove the authenticatorSelection key so that other authentication options will be available. 26 delete requestCredentialOptions.publicKey.authenticatorSelection; 27 28 return await navigator.credentials.create(requestCredentialOptions); 29 } 30 31 async completeRegistration(publicKeyCredential) { 32 const transformedCredentials = { 33 id : publicKeyCredential.id, 34 response : { 35 attestationObject : this.bufferToBase64Url(publicKeyCredential.response.attestationObject), 36 clientDataJSON : this.bufferToBase64Url(publicKeyCredential.response.clientDataJSON) 37 }, 38 transports : (typeof publicKeyCredential.response.getTransports === 'undefined') ? [] : publicKeyCredential.response.getTransports(), 39 type : publicKeyCredential.type 40 }; 41 42 return await this.executeHttpRequest('createPasskeyComplete', { 43 credential : transformedCredentials 44 }); 45 } 46 47 async startPasskeyAssertion() { 48 const response = await this.executeHttpRequest('startPasskeyAssertion'); 49 if (response.result === false) { 50 return response; 51 } 52 53 const requestCredentialOptions = { 54 publicKey : { 55 ...response.data.options, 56 challenge : this.base64UrlToBuffer(response.data.options.challenge), 57 allowCredentials : response.data.options.allowCredentials?.map(credential => { 58 return { 59 ...credential, 60 id : this.base64UrlToBuffer(credential.id) 61 } 62 }) ?? [] 63 } 64 }; 65 66 return await navigator.credentials.get(requestCredentialOptions); 67 } 68 69 async preparePasskeyAssertionCompletionPayload(publicKeyCredential) { 70 return { 71 id : publicKeyCredential.id, 72 response : { 73 authenticatorData : this.bufferToBase64Url(publicKeyCredential.response.authenticatorData), 74 clientDataJSON : this.bufferToBase64Url(publicKeyCredential.response.clientDataJSON), 75 signature : this.bufferToBase64Url(publicKeyCredential.response.signature), 76 userHandle : this.bufferToBase64Url(publicKeyCredential.response.userHandle) ?? undefined 77 }, 78 type : publicKeyCredential.type, 79 clientExtensionResults : publicKeyCredential.getClientExtensionResults() 80 }; 81 } 82 83 setExtraPayloadData(extraPayloadData) { 84 this.extraPayloadData = extraPayloadData; 85 } 86 87 async executeHttpRequest(method, requestParams = {}) { 88 return await $.ajax({ 89 url : '/?' + $.param({ 90 option : 'com_ajax', 91 plugin : method, // ajaxfusionauth 92 format : 'json' 93 }), 94 method : 'POST', 95 data : { 96 ...requestParams, 97 ...this.extraPayloadData 98 }, 99 dataType : 'json' 100 }) 101 .then((response) => response.data[response.data.length - 1]); 102 } 103 104 base64UrlToBuffer(base64Url) { 105 const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); 106 const padLen = (4 - (base64.length % 4)) % 4; 107 return Uint8Array.from(atob(base64.padEnd(base64.length + padLen, '=')), char => char.charCodeAt(0)); 108 } 109 110 bufferToBase64Url(buffer) { 111 let string = ''; 112 const bytes = new Uint8Array(buffer);
113 bytes.forEach(byte => string += String.fromCharCode(byte)); 114 return btoa(string).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); 115 } 116}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.