yubikey
YubiKey OTP offline authentication action
yubikey
Validates a Yubico OTP presented as a RADIUS PAP response by decrypting it locally using the AES-128 key stored in yubikey.secret. No network call is made; validation is fully offline.
This action is only for offline validation. If you use the yubikey HTTP backend for cloud validation, configure secret on the backend block instead of this action.
The action extracts the public UID from the token identifier prefix and the 32-character modhex ciphertext, decrypts the OTP, and verifies the CRC16 checksum, private UID, and replay-protection counters.
Basic Syntax
YubiKey OTP is typically used as a second factor alongside a static password. The user enters their password and OTP concatenated in the PAP password field (<password><otp>). Use secret together with the yubikey() filter to extract the right portion of the field for each action:
@execute {
backend {
name "USERS";
query "FIND_USER";
}
# Validate the static password portion that sits before the OTP
pap {
secret radius.request.password | yubikey(password-otp, password);
}
# Validate the YubiKey OTP portion
yubikey {
secret radius.request.password | yubikey(password-otp, otp);
}
}
These examples omit the counter update step for brevity. See Replay Protection for the complete pattern including counter persistence.
For OTP-only authentication (single factor — possession only), omit secret:
yubikey;
Parameters
secret
Expression that produces the OTP value the action will validate. Use it together with the yubikey() filter to extract the OTP from a combined <password><otp> PAP field, or with any other expression that yields a Yubico OTP string.
When secret is set, its value replaces the protocol PAP response that the action would otherwise read. This is the recommended way to point the action at the right value.
range
Slices the PAP response by character offsets before passing it to the OTP
validator. Prefer secret with the yubikey() filter
for new configurations; range is limited to a single contiguous slice and
remains supported mainly for backward compatibility.
Syntax: range <start> <end> [exclusive]
<start>- Starting index (negative values count from end)<end>- Ending index (negative values count from end)exclusive- Optional keyword to invert the range and keep everything except the specified slice- Indices are 0-based
# Validate only the trailing 44 characters as the YubiKey OTP
yubikey {
range -44 0;
}
A standard Yubico OTP is 44 characters (12-character modhex public UID + 32-character ciphertext).
Backend Mapping
The backend must populate yubikey context variables before the yubikey action runs. At minimum, yubikey.secret (the AES-128 key) is required. Optional variables enable public/private UID checks and replay detection.
For a complete backend schema, query examples, and counter update configuration, see Offline Validation Configuration in the YubiKey Authentication article.
Replay Protection
When yubikey.counter is set, the action compares the decrypted usage counter against the stored value:
- Counter is lower than stored: rejected as replayed OTP.
- Counter equals stored: session counter is compared; lower value is rejected.
- Counter is higher than stored: accepted; both counters are updated in the context.
Persist the updated counters after successful authentication to prevent replay on the next request. See Offline Validation Configuration for the complete pipeline pattern.
Related
yubikeycontext variables - All variables read and written by this actionyubikeyHTTP backend - Delegates OTP validation to an external Yubico serverpapaction - PAP password validation; combine for password+OTP- YubiKey Authentication article - Architecture overview and deployment patterns