Getting Started
Real quick-start guide for react-native-yubikit
Install
npm install @doko/react-native-yubikit
# or
yarn add @doko/react-native-yubikitThen complete the platform setup in Installation - iOS needs a Podfile override before it will build.
The fastest path: YubiKeyProvider + useYubiKey()
The library ships a YubiKeyProvider and matching useYubiKey() hook that handle discovery state for you - device list, auto-selection of the most recently attached device, USB/NFC discovery toggles, and the last discovery error:
import { YubiKeyProvider, useYubiKey } from '@doko/react-native-yubikit';
export default function App() {
return (
<YubiKeyProvider>
<DeviceScreen />
</YubiKeyProvider>
);
}
function DeviceScreen() {
const {
devices,
selectedDevice,
selectDevice,
isUsbDiscovering,
startUsbDiscovery,
stopUsbDiscovery,
isNfcDiscovering,
startNfcDiscovery,
stopNfcDiscovery,
lastError,
} = useYubiKey();
// startUsbDiscovery({ handlePermissions: true }) to begin listening,
// then read/write against selectedDevice.handle with any module below.
}useYubiKey() must be called under a <YubiKeyProvider> - it throws otherwise. The provider also stops any discovery it started when it unmounts, so it can't leave a dangling USB/NFC session running after your component tree goes away.
If you need different state shape (e.g. starting NFC and USB together automatically, or keeping every attached device instead of most-recent-first), see Advanced Patterns for building a custom hook around Core.addYubiKeyListener instead.
The mental model
Whether you use the shipped hook or roll your own, the library doesn't give you a single YubiKeyReader class - underneath, everything follows the same shape:
- Start discovery (
Core.startUsbDiscovery/Core.startNfcDiscovery, orstartUsbDiscovery/startNfcDiscoveryfromuseYubiKey()). - Listen for
attached/detached/errorevents (Core.addYubiKeyListener, or read them offuseYubiKey()'sdevices/lastError). - Once you have a device's
handle, call functions from the module you need (Oath,Piv,OpenPgp,YubiOtp,Fido,Management,Support) directly with thathandle- the modules manage their own connection internally. Core.requestConnection/Core.sendApdu/Core.closeConnectionare a lower-level escape hatch for raw APDU access; you don't need them for normal OATH/PIV/FIDO2/etc. usage.
Discover a device without the hook
import { Core } from '@doko/react-native-yubikit';
import type { YubiKeyDevice, YubiKeyEvent } from '@doko/react-native-yubikit';
const subscription = Core.addYubiKeyListener((event: YubiKeyEvent) => {
switch (event.type) {
case 'attached':
console.log('YubiKey attached:', event.device);
break;
case 'detached':
console.log('YubiKey detached:', event.handle);
break;
case 'error':
console.error('Discovery error:', event.error);
break;
}
});
// USB discovery - requests OS-level permission on Android automatically
Core.startUsbDiscovery({ handlePermissions: true });
// NFC discovery (needs the Info.plist / capability setup from Installation, on iOS)
Core.startNfcDiscovery();
// later, e.g. on unmount:
Core.stopUsbDiscovery();
Core.stopNfcDiscovery();
subscription.remove();You can also poll for whatever is already connected:
const devices: YubiKeyDevice[] = Core.getDiscoveredDevices();Read basic device info
import { Support } from '@doko/react-native-yubikit';
async function describeDevice(device: YubiKeyDevice) {
const info = await Support.readInfo(device.handle);
const name = Support.getName(info); // synchronous, not a Promise
console.log(`${name} - serial ${info.serialNumber}`);
}Read OATH (TOTP/HOTP) codes
import { Oath } from '@doko/react-native-yubikit';
async function readCodes(device: YubiKeyDevice) {
const codes = await Oath.calculateCodes(device.handle);
console.log(codes);
}FIDO2 registration and authentication
import { Fido } from '@doko/react-native-yubikit';
async function register(device: YubiKeyDevice) {
const credential = await Fido.makeCredential(
device.handle,
{
rp: { id: 'example.com', name: 'Example App' },
user: { id: base64UserId, name: 'user@example.com', displayName: 'User' },
challenge: base64Challenge,
pubKeyCredParams: [{ type: 'public-key', alg: -7 }],
// see the PublicKeyCredentialCreationOptions type for the full WebAuthn-shaped
// option set - binary fields (challenge, user.id, credential ids) are base64
// strings on this bridge, not ArrayBuffers
},
'example.com', // effectiveDomain
pin, // optional - only needed if user verification requires a PIN
);
// send `credential` to your server for verification
}Fido.getAssertion follows the same shape for authentication. Credential management functions (getCredentialCount, getRpIdList, getCredentials, deleteCredential, updateUserInformation) only work on Android - the iOS SDK doesn't expose them.
Error handling
There's no custom error class - rejections are plain RN bridge errors with a .message, tagged with one of a fixed set of native error codes (CONNECTION_ERROR, APDU_ERROR, MANAGEMENT_ERROR, OATH_ERROR, PIV_ERROR, OPENPGP_ERROR, YUBIOTP_ERROR, FIDO_ERROR, SUPPORT_ERROR):
try {
const codes = await Oath.calculateCodes(device.handle);
} catch (error) {
console.error('OATH read failed:', String(error));
}