- Added a button for logging in with Passkeys in `login.blade.php` - Refactored the `register` method and added the `login` method in `auth.js` - Made various modifications and additions to the passkey functionality in `PasskeysController.php` - Added event listener for login-passkey element in `app.js` - Modified the passkeys table schema and made modifications to `Passkey.php` - Changed the redirect route in the `login` method of `AuthController.php` - Made modifications and additions to the routes in `web.php` - Added `"web-auth/webauthn-lib": "^4.7"` to the list of required packages in `composer.json` - Changed the redirect URL in `AdminTest.php`
167 lines
5.6 KiB
JavaScript
167 lines
5.6 KiB
JavaScript
class Auth {
|
|
constructor() {}
|
|
|
|
async register() {
|
|
const createOptions = await this.getCreateOptions();
|
|
|
|
const publicKeyCredentialCreationOptions = {
|
|
challenge: this.base64URLStringToBuffer(createOptions.challenge),
|
|
rp: {
|
|
id: createOptions.rp.id,
|
|
name: createOptions.rp.name,
|
|
},
|
|
user: {
|
|
id: new TextEncoder().encode(window.atob(createOptions.user.id)),
|
|
name: createOptions.user.name,
|
|
displayName: createOptions.user.displayName,
|
|
},
|
|
pubKeyCredParams: createOptions.pubKeyCredParams,
|
|
excludeCredentials: [],
|
|
authenticatorSelection: createOptions.authenticatorSelection,
|
|
timeout: 60000,
|
|
};
|
|
|
|
const credential = await navigator.credentials.create({
|
|
publicKey: publicKeyCredentialCreationOptions
|
|
});
|
|
if (!credential) {
|
|
throw new Error('Error generating a passkey');
|
|
}
|
|
|
|
const authenticatorAttestationResponse = {
|
|
id: credential.id ? credential.id : null,
|
|
type: credential.type ? credential.type : null,
|
|
rawId: credential.rawId ? this.bufferToBase64URLString(credential.rawId) : null,
|
|
response: {
|
|
attestationObject: credential.response.attestationObject ? this.bufferToBase64URLString(credential.response.attestationObject) : null,
|
|
clientDataJSON: credential.response.clientDataJSON ? this.bufferToBase64URLString(credential.response.clientDataJSON) : null,
|
|
}
|
|
};
|
|
|
|
const registerCredential = await window.fetch('/admin/passkeys/register', {
|
|
method: 'POST',
|
|
body: JSON.stringify(authenticatorAttestationResponse),
|
|
cache: 'no-cache',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
|
|
},
|
|
});
|
|
|
|
if (!registerCredential.ok) {
|
|
throw new Error('Error saving the passkey');
|
|
}
|
|
|
|
window.location.reload();
|
|
}
|
|
|
|
async getCreateOptions() {
|
|
const response = await fetch('/admin/passkeys/register', {
|
|
method: 'GET',
|
|
});
|
|
|
|
return await response.json();
|
|
}
|
|
|
|
async login() {
|
|
const loginData = await this.getLoginData();
|
|
|
|
const publicKeyCredential = await navigator.credentials.get({
|
|
publicKey: {
|
|
challenge: this.base64URLStringToBuffer(loginData.challenge),
|
|
userVerification: loginData.userVerification,
|
|
timeout: 60000,
|
|
}
|
|
});
|
|
|
|
if (!publicKeyCredential) {
|
|
throw new Error('Authentication failed');
|
|
}
|
|
|
|
const authenticatorAttestationResponse = {
|
|
id: publicKeyCredential.id ? publicKeyCredential.id : '',
|
|
type: publicKeyCredential.type ? publicKeyCredential.type : '',
|
|
rawId: publicKeyCredential.rawId ? this.bufferToBase64URLString(publicKeyCredential.rawId) : '',
|
|
response: {
|
|
authenticatorData: publicKeyCredential.response.authenticatorData ? this.bufferToBase64URLString(publicKeyCredential.response.authenticatorData) : '',
|
|
clientDataJSON: publicKeyCredential.response.clientDataJSON ? this.bufferToBase64URLString(publicKeyCredential.response.clientDataJSON) : '',
|
|
signature: publicKeyCredential.response.signature ? this.bufferToBase64URLString(publicKeyCredential.response.signature) : '',
|
|
userHandle: publicKeyCredential.response.userHandle ? this.bufferToBase64URLString(publicKeyCredential.response.userHandle) : '',
|
|
},
|
|
};
|
|
|
|
const loginAttempt = await window.fetch('/login/passkey', {
|
|
method: 'POST',
|
|
body: JSON.stringify(authenticatorAttestationResponse),
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
|
|
},
|
|
});
|
|
|
|
if (!loginAttempt.ok) {
|
|
throw new Error('Login failed');
|
|
}
|
|
|
|
window.location.assign('/admin');
|
|
}
|
|
|
|
async getLoginData() {
|
|
const response = await fetch('/login/passkey', {
|
|
method: 'GET',
|
|
});
|
|
|
|
return await response.json();
|
|
}
|
|
|
|
/**
|
|
* Convert a base64 URL string to a buffer.
|
|
*
|
|
* Sourced from https://github.com/MasterKale/SimpleWebAuthn/blob/master/packages/browser/src/helpers/base64URLStringToBuffer.ts#L8
|
|
*
|
|
* @param {string} base64URLString
|
|
* @returns {ArrayBuffer}
|
|
*/
|
|
base64URLStringToBuffer(base64URLString) {
|
|
// Convert from Base64URL to Base64
|
|
const base64 = base64URLString.replace(/-/g, '+').replace(/_/g, '/');
|
|
/**
|
|
* Pad with '=' until it's a multiple of four
|
|
* (4 - (85 % 4 = 1) = 3) % 4 = 3 padding
|
|
* (4 - (86 % 4 = 2) = 2) % 4 = 2 padding
|
|
* (4 - (87 % 4 = 3) = 1) % 4 = 1 padding
|
|
* (4 - (88 % 4 = 0) = 4) % 4 = 0 padding
|
|
*/
|
|
const padLength = (4 - (base64.length % 4)) % 4;
|
|
const padded = base64.padEnd(base64.length + padLength, '=');
|
|
// Convert to a binary string
|
|
const binary = window.atob(padded);
|
|
// Convert binary string to buffer
|
|
const buffer = new ArrayBuffer(binary.length);
|
|
const bytes = new Uint8Array(buffer);
|
|
for (let i = 0; i < binary.length; i++) {
|
|
bytes[i] = binary.charCodeAt(i);
|
|
}
|
|
return buffer;
|
|
}
|
|
|
|
/**
|
|
* Convert a buffer to a base64 URL string.
|
|
*
|
|
* Sourced from https://github.com/MasterKale/SimpleWebAuthn/blob/master/packages/browser/src/helpers/bufferToBase64URLString.ts#L7
|
|
*
|
|
* @param {ArrayBuffer} buffer
|
|
* @returns {string}
|
|
*/
|
|
bufferToBase64URLString(buffer) {
|
|
const bytes = new Uint8Array(buffer);
|
|
let str = '';
|
|
for (const charCode of bytes) {
|
|
str += String.fromCharCode(charCode);
|
|
}
|
|
const base64String = btoa(str);
|
|
return base64String.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
|
}
|
|
}
|
|
|
|
export { Auth };
|