- BAS WebAuthn REST API
- Client-Server Workflow
- Module webauth.lua
- Database Schema
- Quarantined Table for New Registrations
This is the REST API provided by the webauthn.lua module. It is compatible with the SimpleWebAuthn JavaScript library.
All REST API endpoints may return the following JSON response on failure:
json { "ok": false, "msg": "error-code" }
Where error-code
can be:
Invalidrequest
- The provided data is incorrect.webautherr
- WebAuthn decoding/verification error.notfound
- User not found.404
- Web service not found.
POST /webauthn/finduser
{ "user": "username" }
{ "ok": true }
if found, { "ok": false, "msg": "quarantined" }
if found, but requires validation, or { "ok": false, "msg": "notfound" }
if the user does not exist.
POST /webauthn/regoptions
{ "user": "username" }
Returns registration options needed for startRegistration() in SimpleWebAuthn.
POST /webauthn/register
Takes WebAuthn attestation data from startRegistration().
{ "ok": true, "msg": "message from register callback" }
or
{ "ok": false, "msg": "error-code" }
where error-code
can be:
Invalidrequest
webautherr
- A response message from the server-side
register
callback
POST /webauthn/authoptions
{ "user": "username" }
Returns authentication options needed for startAuthentication() in SimpleWebAuthn.
or:
{ "ok": false, "msg": "quarantined" }
if found, but requires validation, or { "ok": false, "msg": "notfound" }
if the user does not exist.
POST /webauthn/authenticate
Takes WebAuthn assertion data from startAuthentication().
{ "ok": true, "msg": "message from authenticate callback" }
or
{ "ok": false, "msg": "error-code" }
where error-code
can be:
Invalidrequest
webautherr
- A response message from the server-side
authenticate
callback .
- User enters their username in the login form.
- Check if the user exists (
webauthn/finduser
) or use (webauthn/authoptions
).- If not found, show the registration button .
- If found, proceed to authentication.
- User registers (if needed):
- Request registration options (
webauthn/regoptions
). - Call startRegistration() .
- Submit
startRegistration()
data towebauthn/register
.
- Request registration options (
- User logs in:
- Request authentication options (
webauthn/authoptions
). - Call startAuthentication() .
- Submit
startAuthentication()
data towebauthn/authenticate
.
- Request authentication options (
- Server verifies the authentication data using the client's public key.
To initialize a server-side WebAuthn instance, load the module and call the create
function:
webauthn, wadir = require"webauthn".create{
register = function(),
registered = function(),
authenticate = function(),
loginerr = function()
}
The create
function accepts a table with several callback functions. For details on required and optional callback functions, see the Callback API.
The create
function returns:
webauthn
- The WebAuthn instance.wadir
- The directory for the WebAuthn REST API. Thewadir
directory must be installed into BAS's Virtual File System to expose the WebAuthn REST API.
The WebAuthn instance provides the following API methods for managing user authentication data.
Returns:
- The Users Table, containing all registered users and their authenticators.
Usage Notes:
- Your application is responsible for reading, serializing, and persistently saving this data.
Parameters:
users
- The Users Table to be installed.
Usage Notes:
- This method is typically called at startup to load the persisted Users Table into the WebAuthn instance.
Parameters:
username
- The username to check.
Returns:
- The User Table if the user has a quarantined authenticator.
nil
if the user has no quarantined authenticators.
Note: Only the authenticator device is quarantined, not the user.
- A user may register multiple authenticators, and another authenticator device may have already been validated.
Parameters:
url
- The validation URL provided to theregister
callback when the authenticator was registered.
Returns:
- The username if the quarantined authenticator was successfully validated.
nil
if no matching quarantined authenticator is found.
Usage Notes:
- This method moves a quarantined authenticator from the quarantined table to the Users Table after validation.
This API provides essential methods for managing WebAuthn user registrations, quarantined authenticators, and database persistence within a BAS-powered application.
The required and optional callback functions passed into function require"webauthn".create().
-
Registration data is sent to the
register
callback(username
,user
,rawId
,url
) after being successfully validated.The callback must return:
ok
(boolean) -true
if the registration is to be accepted,false
otherwise.accept
(boolean):true
: The user is immediately allowed to log in.false
: The user is placed in thequarantined
table for further approval.
msg
(string) - optional response sent to browser.
Note
- If the function returns
false, x, [msg]
then{ "ok": false, "msg": msg | "" }
is sent to the browser. - If the function returns
true, false
then{ "ok": false, "msg": "quarantined" }
is sent to the browser. - If the function returns
true, true, [msg]
then{ "ok": true, "msg": msg or "" }
is sent to the browser and the callbackregistered
is called.
-
The
registered
callback(username
[,cmd
]) is called when the user completes the registration process.- If the second return value from the
register
callback was true, this callback is called with callback(username
). - If the second return value from the
register
callback was false, this callback is called with callback(username
,cmd
) when the user completes the registration process. The callback must respond by rendering a web page or redirecting the user to for example the login page. Argumentcmd
is the combined request and response object.
- If the second return value from the
- Authentication data is sent to the
authenticate
callback(username
,user
,rawId
) after being successfully validated. - The callback must return:
ok
(boolean) -true
if authentication is accepted,false
otherwise.msg
(string) - optional response sent to browser.
- Any type of login validation failure triggers the
loginerr
callback(message). - This callback receives a failure message detailing the reason for rejection.
The following schema explains the webauth
data structure. It is the format you must use when calling webauth:set(data)
, and it is the format returned when calling webauth:get()
. The structure is implemented as Lua tables, with the following key principles:
- Each user is identified by a unique username, which serves as the key in the
users
table. - The username does not have to be an email address.
- If an email address is used as a username, it must be normalized externally before being stored in this structure.
- A single user can register multiple authenticators (e.g., security keys, biometric sensors on different devices).
- Each authenticator is stored within the user's table, indexed by its
rawId
.
Each authenticator entry contains:
- key:
rawId
: The unique identifier for the authenticator.
- value - a table:
signatureCounter
: A counter used to detect cloned authenticators.pubKey
: The public key for ES256 (ECDSA P-256, SHA-256), stored as{x, y}
coordinates.createdAt
: A timestamp marking when the authenticator was registered.lastUsedAt
: A timestamp recording the last successful authentication.
-- Table 'users': Key-value table where the key is string:user (username)
users = {
["username-1"] = { -- user-table
-- Key-value table where the key is binstring:rawId (authenticator ID)
["rawId-1"] = { -- authenticator data
signatureCounter = 0,
pubKey = { x = binstring, y = binstring }, -- ES256 (P-256 ECDSA)
createdAt = 1700000000, -- UNIX timestamp
lastUsedAt = 1700000100 -- UNIX timestamp
},
["rawId-2"] = {
signatureCounter = 88,
pubKey = { x = binstring, y = binstring },
createdAt = 1700000050,
lastUsedAt = 1700000200
}
}
}
A binstring (binary string) is simply a Lua string that contains raw binary data instead of human-readable text. In Lua, binary data can be stored in strings because Lua strings are byte sequences, not null-terminated character arrays like in C.
JSON does not support binary data, so storing binstring values (e.g., rawId and public keys) in JSON will corrupt them or require inefficient Base64 encoding. For proper serialization of the users
table, use CBOR or UBJSON, both of which natively support binary data.
-
A separate
quarantined
table exists, with the following structure:quarantined={ ["url-1"] = {username,user-table} }
New user registrations are stored here if the registration callback returns
true,false
.