-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
DAPP1-35-auth0-webauthn-integration (#61)
- Loading branch information
Showing
6 changed files
with
270 additions
and
32 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { PublicKeyCredentialCreationOptions, PublicKeyCredential } from 'src/types/webauthn'; | ||
|
||
export const startRegistration = async (): Promise<PublicKeyCredentialCreationOptions> => { | ||
const response = await fetch('/api/webauthn/register/challenge', { | ||
method: 'POST', | ||
credentials: 'include' | ||
}); | ||
return await response.json(); | ||
}; | ||
|
||
export const completeRegistration = async (credential: PublicKeyCredential) => { | ||
const response = await fetch('/api/webauthn/register', { | ||
method: 'POST', | ||
body: JSON.stringify(credential), | ||
headers: { | ||
'Content-Type': 'application/json' | ||
}, | ||
credentials: 'include' | ||
}); | ||
return await response.json(); | ||
}; | ||
|
||
export const startAuthentication = async (): Promise<PublicKeyCredentialCreationOptions> => { | ||
const response = await fetch('/api/webauthn/authenticate/challenge', { | ||
method: 'POST', | ||
credentials: 'include' | ||
}); | ||
return await response.json(); | ||
}; | ||
|
||
export const completeAuthentication = async (assertion: PublicKeyCredential) => { | ||
const response = await fetch('/api/webauthn/authenticate', { | ||
method: 'POST', | ||
body: JSON.stringify(assertion), | ||
headers: { | ||
'Content-Type': 'application/json' | ||
}, | ||
credentials: 'include' | ||
}); | ||
return await response.json(); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import React from 'react'; | ||
import { useAuth0 } from '@auth0/auth0-react'; | ||
|
||
const AuthButton: React.FC = () => { | ||
const { loginWithRedirect, logout, isAuthenticated } = useAuth0(); | ||
|
||
return ( | ||
<div> | ||
{isAuthenticated ? ( | ||
<button onClick={() => logout()}> | ||
Logout | ||
</button> | ||
) : ( | ||
<button onClick={() => loginWithRedirect()}> | ||
Login with WebAuthn (Auth0) | ||
</button> | ||
)} | ||
</div> | ||
); | ||
}; | ||
|
||
export default AuthButton; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
import React, { createContext, useState, useContext, ReactNode } from 'react'; | ||
import { completeAuthentication, completeRegistration, startAuthentication, startRegistration } from 'src/api/webauthn'; | ||
import { PublicKeyCredentialCreationOptions, PublicKeyCredentialRequestOptions, PublicKeyCredential } from 'src/types/webauthn'; | ||
|
||
interface AuthContextType { | ||
isAuthenticated: boolean; | ||
authenticate: () => Promise<void>; | ||
register: () => Promise<void>; | ||
} | ||
|
||
interface AuthProviderProps { | ||
children: ReactNode; | ||
} | ||
|
||
const AuthContext = createContext<AuthContextType | undefined>(undefined); | ||
|
||
// Utility to convert base64 to Uint8Array | ||
const base64ToUint8Array = (base64String: string): Uint8Array => { | ||
const decodedString = atob(base64String); | ||
const bytes = new Uint8Array(decodedString.length); | ||
for (let i = 0; i < decodedString.length; i++) { | ||
bytes[i] = decodedString.charCodeAt(i); | ||
} | ||
return bytes; | ||
}; | ||
|
||
export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => { | ||
const [isAuthenticated, setIsAuthenticated] = useState(false); | ||
|
||
// Register function | ||
const register = async () => { | ||
try { | ||
const registrationOptions: PublicKeyCredentialCreationOptions = await startRegistration(); | ||
|
||
if (!navigator.credentials) { | ||
throw new Error('WebAuthn is not supported in this browser.'); | ||
} | ||
|
||
const credential = await navigator.credentials.create({ publicKey: registrationOptions }); | ||
|
||
if (credential) { | ||
await completeRegistration(credential as PublicKeyCredential); | ||
} else { | ||
throw new Error('Registration failed. Please try again.'); | ||
} | ||
} catch (error) { | ||
console.error('Error during registration:', error); | ||
alert('Registration failed. Please try again.'); | ||
} | ||
}; | ||
|
||
// Updated authenticate function | ||
const authenticate = async () => { | ||
try { | ||
// Start authentication process | ||
const authenticationOptions: PublicKeyCredentialRequestOptions = await startAuthentication(); | ||
|
||
// Convert challenge from base64 to Uint8Array | ||
authenticationOptions.challenge = base64ToUint8Array(authenticationOptions.challenge as unknown as string); | ||
|
||
if (!navigator.credentials) { | ||
throw new Error('WebAuthn is not supported in this browser.'); | ||
} | ||
|
||
// Start WebAuthn authentication | ||
const assertion = await navigator.credentials.get({ publicKey: authenticationOptions }); | ||
|
||
if (assertion) { | ||
const response = await completeAuthentication(assertion as PublicKeyCredential); | ||
setIsAuthenticated(response.success); | ||
} else { | ||
throw new Error('Authentication failed. Please try again.'); | ||
} | ||
|
||
} catch (error) { | ||
console.error('Error during authentication:', error); | ||
alert('Authentication failed. Please try again.'); | ||
} | ||
}; | ||
|
||
return ( | ||
<AuthContext.Provider value={{ isAuthenticated, authenticate, register }}> | ||
{children} | ||
</AuthContext.Provider> | ||
); | ||
}; | ||
|
||
export const useAuth = () => { | ||
const context = useContext(AuthContext); | ||
if (!context) { | ||
throw new Error('useAuth must be used within an AuthProvider'); | ||
} | ||
return context; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
// src/types/webauthn.d.ts | ||
|
||
// PublicKeyCredentialCreationOptions for registration | ||
interface PublicKeyCredentialCreationOptions { | ||
challenge: Uint8Array | ArrayBuffer; | ||
rp: { | ||
name: string; | ||
id?: string; | ||
}; | ||
user: { | ||
id: Uint8Array; | ||
name: string; | ||
displayName: string; | ||
}; | ||
pubKeyCredParams: Array<{ | ||
type: 'public-key'; | ||
alg: number; | ||
}>; | ||
authenticatorSelection?: { | ||
authenticatorAttachment?: 'platform' | 'cross-platform'; | ||
residentKey?: 'required' | 'preferred' | 'discouraged'; | ||
userVerification?: 'required' | 'preferred' | 'discouraged'; | ||
}; | ||
attestation?: 'none' | 'indirect' | 'direct' | 'enterprise'; | ||
timeout?: number; | ||
excludeCredentials?: Array<{ | ||
id: Uint8Array; | ||
type: 'public-key'; | ||
}>; | ||
} | ||
|
||
// PublicKeyCredentialRequestOptions for authentication | ||
interface PublicKeyCredentialRequestOptions { | ||
challenge: Uint8Array | ArrayBuffer; | ||
timeout?: number; | ||
rpId?: string; | ||
allowCredentials?: Array<{ | ||
id: Uint8Array; | ||
type: 'public-key'; | ||
transports?: Array<'usb' | 'nfc' | 'ble' | 'internal'>; | ||
}>; | ||
userVerification?: 'required' | 'preferred' | 'discouraged'; | ||
} | ||
|
||
// Extend the Credential interface to include public-key credentials | ||
interface PublicKeyCredential extends Credential { | ||
rawId: ArrayBuffer; | ||
response: AuthenticatorAttestationResponse | AuthenticatorAssertionResponse; | ||
clientExtensionResults: () => AuthenticationExtensionsClientOutputs; | ||
} | ||
|
||
// AuthenticatorAttestationResponse used during registration | ||
interface AuthenticatorAttestationResponse { | ||
clientDataJSON: ArrayBuffer; | ||
attestationObject: ArrayBuffer; | ||
} | ||
|
||
// AuthenticatorAssertionResponse used during authentication | ||
interface AuthenticatorAssertionResponse { | ||
clientDataJSON: ArrayBuffer; | ||
authenticatorData: ArrayBuffer; | ||
signature: ArrayBuffer; | ||
userHandle?: ArrayBuffer; | ||
} | ||
|
||
// Exported types for use in other parts of the application | ||
export { | ||
PublicKeyCredentialCreationOptions, | ||
PublicKeyCredentialRequestOptions, | ||
PublicKeyCredential, | ||
AuthenticatorAttestationResponse, | ||
AuthenticatorAssertionResponse, | ||
}; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters